-frontend: Refactored menu event handling and fixed a number of issues. (#8777)

* Moved common code for drawing about box, info viewer, and other text box menus to a base class; removed the last of the info viewer logic and the multi-line item hack from the base menu class.
* Added previous/next group navigation for general inputs and plugin input selection menus.
* Moved message catalog logic to lib/util, allowing osd and emu to use localised messages.
* Made the base menu class use the UI manager’s feature for holding session state rather than a static map and mutex.
* Improved menu event handling model, and fixed many issues, particularly with menus behaving badly when hidden/shown.
* Added better support for menus that don’t participate in the usual menu stack, like the menuless sliders and the save/load state menus.
* Made a number of menus refresh state when being shown after being hidden (fixes MT08121 among other issues).
* Fixed indication of mounted slot option in the slot option details menu.
* Improved appearance of background menus when emulation isn't running - draw all menus in the stack, and darken the background menus to make the edges of the active menu clearer.
* Fixed locale issues in -listxml.

-debugger: Made GUI debuggers more uniform.
* Added new memory view features to Win32 debugger.
* Fixed spelling of hexadecimal in Cocoa debugger and added decimal address option.
* Fixed duplicate keyboard shortcut in Cocoa debugger (Shift-Cmd-D was both new device window and 64-bit float format).
* Made keyboard shortcuts slightly more consistent across debuggers.

-plugins: Moved input selection menu and sequence polling code to a common library.  Fixed the issue that prevented keyboard inputs being mapped with -steadykey on.

-docs: Started adding some documentation for MAME's internal UI, and updated the list of example front-ends.

-Regenerated message catalog sources.  For translators, the new strings are mostly:
* The names of the inputs provided by the OS-dependent layer for things like fullscreen and video features. These show up in the user interface inputs menu.
* The names for automatically generated views. These show up in the video options menu - test with a system with a lot of screens to see more variants.
* The input macro plugin UI.
* A few format strings for analog input assignments.
* A few strings for the about box header.
This commit is contained in:
Vas Crabb 2021-10-31 12:31:16 +11:00 committed by GitHub
parent cfffc54b61
commit d64ea5331b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
174 changed files with 84805 additions and 63693 deletions

View File

@ -16,6 +16,10 @@ To set the folders where the data plugin looks for supported files, choose
**Configure Directories**, and then choose **DATs**. You can also set the
``historypath`` option in your **ui.ini** file.
Loading large data files like **history.xml** can take quite a while, so please
be patient the first time you start MAME after updating or adding new data
files.
The following files are supported:
history.xml

View File

@ -436,6 +436,11 @@ ui:get_string_width(str)
ui:set_aggressive_input_focus(enable)
On some platforms, this controls whether MAME should accept input focus in
more situations than when its windows have UI focus.
ui:get_general_input_setting(type, [player])
Gets a description of the configured input sequence for the specified input
type and player suitable for using in prompts. The input type is an
enumerated value. The player number is a zero-based index. If the player
number is not supplied, it is assumed to be zero.
Properties
^^^^^^^^^^

View File

@ -59,10 +59,11 @@ and saving/loading save states.
Change current UI option setting when an arrow is present on it.
**Right Arrow**
Change current UI option setting when an arrow is present on it.
**Home**
Highlight first UI menu option.
**End**
Select last UI menu option.
**Home**/**End**
Highlight first or last UI menu option.
**[** **]**
Move to previous or next group in UI menus that support it (e.g. move to the
inputs for the previous or next device in the Input (this Machine) menu).
**Enter**/**Joystick 1 Button 1**
Select currently highlighted UI menu option.
**Space**

View File

@ -1,20 +1,45 @@
.. _frontends:
Frontends
=========
Front-ends
==========
There are a number of third party tools for MAME to make system and software selection simpler. These tools are called "Frontends", and there are far too many to list conclusively here. Some are free, some are commercial-- caveat emptor. Some older frontends predate the merging of MAME and MESS and do not support the additional console, handheld, etc functionality that MAME inherited from MESS.
A number of third party tools for MAME to make system and software selection
simpler are available. These tools are called “front-ends”, and there are far
too many to list exhaustively here. Some are free, some are commercial
caveat emptor. Some older front-ends predate the merging of MAME and MESS and
do not support the additional console, hand-held, and computer functionality
inherited from MESS.
This following list is not an endorsement of any of these frontends by the MAME team, but simply showing a number of commonly used free frontends as a good starting point to begin from.
This following list is not an endorsement of any of these front-ends by the MAME
team. It simply shows a number of commonly used free front-ends to provide a
starting point.
| QMC2 (multiple platforms)
| Download: http://qmc2.batcom-it.net/
|
| IV/Play (Microsoft Windows)
| Download: http://www.mameui.info/
|
| EmuLoader (Microsoft Windows)
| Download: http://emuloader.mameworld.info/
|
`QMC2 <http://qmc2.batcom-it.net/>`__ (multiple platforms)
Provides a graphical interface for configuring many of MAMEs settings and
features. Also includes ROM management and media auditing features.
Written in C++ using the Qt toolkit, the
`source code is on SourceForge <https://sourceforge.net/projects/qmc2/>`__.
`Negatron <http://negatron.net/>`__ (multiple platforms)
Negatron emphasises features for configuring emulated computers and
consoles. Written in Java, the
`source code is on GitHub <https://github.com/xinyingho/Negatron>`__.
`BletchMAME <https://www.bletchmame.org/>`__ (multiple platforms)
BletchMAME takes advantage of MAMEs Lua scripting interface to integrate
tightly and effectively replace MAMEs internal user interface. It has
many useful features for home computer emulation. Written in C++, the
`source code is on GitHub <https://github.com/npwoods/bletchmame>`__.
`IV/Play <http://www.mameui.info/>`__ (Microsoft Windows)
A simple Windows program for launching systems in MAME. Written in C#, the
`souce code is on GitHub <https://github.com/Mataniko/IV-Play>`__.
`EmuLoader <http://emuloader.mameworld.info/>`__ (Microsoft Windows)
EmuLoader provides a Windows interface for launching systems in multiple
emulators, including MAME, Supermodel and DEMUL. Written in Delphi Pascal,
the source code is available
`on the download page <https://emuloader.mameworld.info/downloads.htm>`__.
`Retrofire <https://e2j.net/downloads/>`__ (Japanese, Microsoft Windows)
Provides a Japanese-language graphical interface for launching systems or
software in MAME.
The MAME team will not provide support for issues with frontends. For support, we suggest contacting the frontend author or trying any of the popular MAME-friendly forums on the internet.
The MAME team will not provide support for issues with front-ends. For support,
we suggest contacting the front-end author or asking on one of the popular
MAME-friendly forums on the Internet.

View File

@ -1,16 +1,18 @@
Basic MAME Usage and Configuration
----------------------------------
This section describes general usage information about MAME. It is intended to cover aspects of using and configuring MAME that are common across all operating systems. For additional OS-specific options, please see the separate documentation for your platform of choice.
This section describes general usage information about MAME. It is intended to
cover aspects of using and configuring MAME that are common across all operating
systems. For additional OS-specific options, please see the separate
documentation for your platform of choice.
.. toctree::
:titlesonly:
usingmame
defaultkeys
mamemenus
frontends
aboutromsets
commonissues
:titlesonly:
usingmame
ui
defaultkeys
mamemenus
frontends
aboutromsets
commonissues

View File

@ -0,0 +1,137 @@
.. _ui:
MAMEs User Interface
=====================
.. contents:: :local:
.. _ui-intro:
Introduction
------------
MAME provides a simple user interface for selecting a system and software to
run and changing settings while running an emulated system. MAMEs user
interface is designed to be usable with a keyboard, game controller, or pointing
device, but will require a keyboard for initial configuration.
The default settings for the most important controls to know when running an
emulated system, and the settings they correspond to in case you want to change
them, are as follows:
Scroll Lock, or Forward Delete on macOS (UI Toggle)
For emulated systems with keyboard inputs, enable or disable UI controls.
(MAME starts with UI controls disabled for systems with keyboard inputs
unless the :ref:`ui_active option <mame-commandline-uiactive>` is on.)
Tab (Config Menu)
Show or hide the menu during emulation.
Escape (UI Cancel)
Return to the system selection menu, or exit if MAME was started with a
system specified (from the command line or using an
:ref:`external front-end <frontends>`).
.. _ui-menus:
Navigating menus
----------------
By default, MAME menus can be navigated using the keyboard cursor keys. All
the UI controls can be changed by going to the **General Inputs** menu and then
selecting **User Interface**. The default keyboard controls on a US ANSI QWERTY
layout keyboard, and the settings they correspond to, are as follows:
Up Arrow (UI Up)
Highlight the previous menu item, or the last item if the first item is
highlighted.
Down Arrow (UI Down)
Highlight the next menu item, or the first item if the last item is
highlighted.
Left Arrow (UI Left)
For menu items that are adjustable settings, reduce the value or select the
previous setting (these menu items show left- and right-facing triangles
beside the value).
Right Arrow (UI Left)
For menu items that are adjustable settings, increase the value or select
the next setting (these menu items show left- and right-facing triangles
beside the value).
Return/Enter keypad Enter (UI Select)
Select the highlighted menu item.
Forward Delete, or Fn+Delete on some compact keyboards (UI Clear)
Clear setting or reset to default value.
Escape (UI Cancel)
Close the menu, returning to the previous menu, or returning to the
emulated machine for the main menu (theres usually an item at the bottom
of the menu for the same purpose).
Home (UI Home)
Highlight the first menu item and scroll to the top of the menu.
End (UI End)
Highlight the last menu item and scroll to the bottom of the menu.
Page Up (UI Page Up)
Scroll the menu up by one screen.
Page Down (UI Page Down)
Scroll the menu down by one screen.
[ (UI Previous Group)
Move to the previous group of items (not used by all menus).
] (UI Next Group)
Move to the next group of items (not used by all menus).
.. _ui-menus-gamectrl:
Using a game controller
~~~~~~~~~~~~~~~~~~~~~~~
MAME supports navigating menus with a game controller or joystick, but only the
most important UI controls have joystick assignments by default:
* Move the first joystick up or down in the Y axis to highlight the previous or
next menu item.
* Move the first joystick left or right in the X axis to adjust settings.
* Press the first button on the first joystick to select the highlighted menu
item.
If you want to be able to use MAME with a game controller without needing a
keyboard, youll need to assign joystick buttons (or combinations of buttons) to
these controls as well:
* **Config Menu** to show or dismiss the menu during emulation
* **UI Cancel** to close menus, return to the system selection menu, or exit
MAME
* **UI Clear** isnt essential for basic emulation, but its used to clear or
reset some settings to defaults
* **UI Home**, **UI End**, **UI Page Up**, **UI Page Down**, **UI Previous
Group** and **UI Next Group** are not essential, but make navigating some
menus easier
If youre not using an external front-end to launch systems in MAME, you should
assign joystick buttons (or combinations of buttons) to these controls to make
full use of the system and software selection menus:
* **UI Focus Next**/**UI Focus Previous** to navigate between panes
* **UI Add Remove favorite**, **UI Export List** and **UI Audit Media** if you
want access to these features without using a keyboard or pointing device
.. _ui-menus-mouse:
Using a mouse or trackball
~~~~~~~~~~~~~~~~~~~~~~~~~~
MAME supports navigating menus using a mouse or trackball that works as a system
pointing device:
* Click menu items to highlight them.
* Double-click menu items to select them.
* Click the left- or right-pointing triangle to adjust settings.
* For menus with too many items to fit on the screen, click the upward- or
downward-pointing triangle at the top or bottom to scroll up or down by one
screen at a time.
* Use vertical scrolling gestures to scroll menus or text boxes with too many
items or lines to fit on the screen.
* Click toolbar items to select them, or hover over them to see a description.
If you have enough additional mouse buttons, you may want to assign button
combinations to the **Config Menu**, **Pause** and/or **UI Cancel** inputs to
make it possible to use MAME without a keyboard.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
local lib = {}
-- Common UI helper library
local commonui
-- Set of all menus
local MENU_TYPES = { MAIN = 0, EDIT = 1, ADD = 2, BUTTON = 3 }
@ -27,14 +30,17 @@ 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
-- Inputs that can be autofired (to list in BUTTON menu)
local inputs
-- Handler for BUTTON menu
local input_menu
-- Returns the section (from MENU_SECTIONS) and the index within that section
local function menu_section(index)
@ -59,19 +65,14 @@ local function is_button_complete(button)
return button.port and button.field and button.key and button.on_frames and button.off_frames and button.button and button.counter
end
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
-- Main menu
local function populate_main_menu(buttons)
local ioport = manager.machine.ioport
local input = manager.machine.input
local menu = {}
menu[#menu + 1] = {_('Autofire buttons'), '', 'off'}
menu[#menu + 1] = {string.format(_('Press %s to delete'), input:seq_name(ioport:type_seq(ioport:token_to_input_type('UI_CLEAR')))), '', 'off'}
menu[#menu + 1] = {_p('plugin-autofire', 'Autofire buttons'), '', 'off'}
menu[#menu + 1] = {string.format(_p('plugin-autofire', 'Press %s to delete'), manager.ui:get_general_input_setting(ioport:token_to_input_type('UI_CLEAR'))), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
@ -82,22 +83,26 @@ local function populate_main_menu(buttons)
freq = 1 / screen.frame_period
end
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 = string.format(_('%s [%g Hz]'), _p('input-name', button.button.name), rate)
local subtext = input:seq_name(button.key)
menu[#menu + 1] = {text, subtext, ''}
if index == initial_button then
main_selection_save = #menu
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 = string.format(_p('plugin-autofire', '%s [%g Hz]'), _p('input-name', button.button.name), rate)
local subtext = input:seq_name(button.key)
menu[#menu + 1] = {text, subtext, ''}
if index == initial_button then
main_selection_save = #menu
end
end
else
menu[#menu + 1] = {_p('plugin-autofire', '[no autofire buttons]'), '', 'off'}
end
initial_button = nil
content_height = #menu
menu[#menu + 1] = {'---', '', ''}
menu[#menu + 1] = {_('Add autofire button'), '', ''}
menu[#menu + 1] = {_p('plugin-autofire', 'Add autofire button'), '', ''}
local selection = main_selection_save
main_selection_save = nil
@ -134,67 +139,53 @@ end
-- Add/edit menus (mostly identical)
local function populate_configure_menu(menu)
local button_name = current_button.button and _p('input-name', current_button.button.name) or _('NOT SET')
local key_name = current_button.key and manager.machine.input:seq_name(current_button.key) or _('NOT SET')
menu[#menu + 1] = {_('Input'), button_name, ''}
local button_name = current_button.button and _p('input-name', current_button.button.name) or _p('plugin-autofire', '[not set]')
local key_name = current_button.key and manager.machine.input:seq_name(current_button.key) or _p('plugin-autofire', '[not set]')
menu[#menu + 1] = {_p('plugin-autofire', 'Input'), button_name, ''}
if not (configure_menu_active or configure_selection_save) then
configure_selection_save = #menu
end
menu[#menu + 1] = {_('Hotkey'), key_name, ''}
menu[#menu + 1] = {_('On frames'), current_button.on_frames, current_button.on_frames > 1 and 'lr' or 'r'}
menu[#menu + 1] = {_('Off frames'), current_button.off_frames, current_button.off_frames > 1 and 'lr' or 'r'}
menu[#menu + 1] = {_p('plugin-autofire', 'Hotkey'), key_name, hotkey_poller and 'lr' or ''}
menu[#menu + 1] = {_p('plugin-autofire', 'On frames'), current_button.on_frames, current_button.on_frames > 1 and 'lr' or 'r'}
menu[#menu + 1] = {_p('plugin-autofire', 'Off frames'), current_button.off_frames, current_button.off_frames > 1 and 'lr' or 'r'}
configure_menu_active = true
end
-- Borrowed from the cheat plugin
local function poll_for_hotkey()
local input = manager.machine.input
local poller = input:switch_sequence_poller()
manager.machine:popmessage(_('Press button for hotkey 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(_('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
return poller.valid and poller.sequence or nil
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
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.field then
initial_input = {port_name = current_button.port, ioport_field = current_button.button}
initial_input = current_button.button
end
return true
end
elseif index == 2 then
-- Hotkey
if event == 'select' then
local keycode = poll_for_hotkey()
if keycode then
current_button.key = keycode
return true
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(_('Number of frames button will be pressed'))
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
@ -207,7 +198,7 @@ local function handle_configure_menu(index, event)
end
elseif index == 4 then
-- Off frames
manager.machine:popmessage(_('Number of frames button will be released'))
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
@ -224,7 +215,7 @@ end
local function populate_edit_menu()
local menu = {}
menu[#menu + 1] = {_('Edit autofire button'), '', 'off'}
menu[#menu + 1] = {_p('plugin-autofire', 'Edit autofire button'), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
@ -232,17 +223,20 @@ local function populate_edit_menu()
content_height = #menu
menu[#menu + 1] = {'---', '', ''}
menu[#menu + 1] = {_('Done'), '', ''}
menu[#menu + 1] = {_p('plugin-autofire', 'Done'), '', ''}
local selection = configure_selection_save
configure_selection_save = nil
return menu, selection, 'lrrepeat'
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
inputs = nil
configure_menu_active = false
table.remove(menu_stack)
return true
@ -254,7 +248,7 @@ end
local function populate_add_menu()
local menu = {}
menu[#menu + 1] = {_('Add autofire button'), '', 'off'}
menu[#menu + 1] = {_p('plugin-autofire', 'Add autofire button'), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
@ -263,20 +257,23 @@ local function populate_add_menu()
menu[#menu + 1] = {'---', '', ''}
if is_button_complete(current_button) then
menu[#menu + 1] = {_('Create'), '', ''}
menu[#menu + 1] = {_p('plugin-autofire', 'Create'), '', ''}
else
menu[#menu + 1] = {_('Cancel'), '', ''}
menu[#menu + 1] = {_p('plugin-autofire', 'Cancel'), '', ''}
end
local selection = configure_selection_save
configure_selection_save = nil
return menu, selection, 'lrrepeat'
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
inputs = nil
configure_menu_active = false
table.remove(menu_stack)
if is_button_complete(current_button) and (event == 'select') then
@ -293,103 +290,31 @@ end
-- Button selection menu
local function populate_button_menu()
local ioport = manager.machine.ioport
menu = {}
menu[#menu + 1] = {_('Select an input for autofire'), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
if not inputs then
inputs = {}
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] = {
port_name = port_key,
field_name = field_key,
ioport_field = field
}
end
end
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)
local i = 1
local prev
while i <= #inputs do
local current = inputs[i]
if (not prev) or (prev.ioport_field.device.tag ~= current.ioport_field.device.tag) then
table.insert(inputs, i, false)
i = i + 2
else
i = i + 1
end
prev = current
end
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 selection = header_height + 1
for i, input in ipairs(inputs) do
if input then
menu[header_height + i] = { _p('input-name', input.ioport_field.name), '', '' }
if initial_input and (initial_input.port_name == input.port_name) and (initial_input.ioport_field.mask == input.ioport_field.mask) and (initial_input.ioport_field.type == input.ioport_field.type) then
selection = header_height + i
initial_input = nil
end
else
local device = inputs[i + 1].ioport_field.device
if device.owner then
menu[header_height + i] = {string.format(_('%s [root%s]'), device.name, device.tag), '', 'heading'}
else
menu[header_height + i] = {string.format(_('[root%s]'), device.tag), '', 'heading'}
end
local function action(field)
if field then
current_button.port = field.port.tag
current_button.field = field.name
current_button.button = field
end
initial_input = nil
input_menu = nil
table.remove(menu_stack)
end
content_height = #menu
initial_input = nil
menu[#menu + 1] = {'---', '', ''}
menu[#menu + 1] = {_('Cancel'), '', ''}
return menu, selection
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)
local section, adjusted_index = menu_section(index)
if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'cancel') then
table.remove(menu_stack)
return true
elseif (section == MENU_SECTIONS.CONTENT) and (event == 'select') then
local selected_input = inputs[adjusted_index]
current_button.port = selected_input.port_name
current_button.field = selected_input.field_name
current_button.button = selected_input.ioport_field
table.remove(menu_stack)
return true
end
return false
return input_menu:handle(index, event)
end
function lib:init_menu(buttons)
@ -397,7 +322,7 @@ function lib:init_menu(buttons)
content_height = 0
menu_stack = { MENU_TYPES.MAIN }
current_button = {}
inputs = nil
input_menu = nil
end
function lib:populate_menu(buttons)

View File

@ -100,14 +100,14 @@ function autofire.startplugin()
if menu_handler then
return menu_handler:populate_menu(buttons)
else
return {{_('Failed to load autofire menu'), '', ''}}
return {{_p('plugin-autofire', 'Failed to load autofire menu'), '', 'off'}}
end
end
emu.register_frame_done(process_frame)
emu.register_start(load_settings)
emu.register_stop(save_settings)
emu.register_menu(menu_callback, menu_populate, _('Autofire'))
emu.register_menu(menu_callback, menu_populate, _p('plugin-autofire', 'Autofire'))
end
return exports

View File

@ -597,58 +597,56 @@ function cheat.startplugin()
local hotkeymenu = false
local hotkeylist = {}
local commonui
local poller
local function menu_populate()
local menu = {}
if hotkeymenu then
local ioport = manager.machine.ioport
local input = manager.machine.input
menu[1] = {_("Select cheat to set hotkey"), "", "off"}
menu[2] = {string.format(_("Press %s to clear hotkey"), input:seq_name(ioport:type_seq(ioport:token_to_input_type("UI_CLEAR")))), "", "off"}
menu[2] = {string.format(_("Press %s to clear hotkey"), manager.ui:get_general_input_setting(ioport:token_to_input_type("UI_CLEAR"))), "", "off"}
menu[3] = {"---", "", "off"}
hotkeylist = {}
local function hkcbfunc(cheat, event)
if event == "clear" then
cheat.hotkeys = nil
return
end
local poller = input:switch_sequence_poller()
manager.machine:popmessage(_("Press button for hotkey 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(_("Invalid sequence entered"))
clearmsg = false
break
if poller then
if poller:poll() then
if poller.sequence then
cheat.hotkeys = { pressed = false, keys = poller.sequence }
end
manager.machine:popmessage(input:seq_name(poller.sequence))
manager.machine.video:frame_update()
poller = nil
return true
end
elseif event == "clear" then
cheat.hotkeys = nil
return true
elseif event == "select" then
if not commonui then
commonui = require('commonui')
end
poller = commonui.switch_polling_helper()
return true
end
if poller.modified and poller.valid then
cheat.hotkeys = { pressed = false, keys = poller.sequence }
end
if clearmsg then
manager.machine:popmessage()
end
manager.machine.video:frame_update()
return false
end
for num, cheat in ipairs(cheats) do
if cheat.script then
menu[#menu + 1] = {cheat.desc, cheat.hotkeys and input:seq_name(cheat.hotkeys.keys) or _("None"), ""}
local setting = cheat.hotkeys and input:seq_name(cheat.hotkeys.keys) or _("None")
menu[#menu + 1] = {cheat.desc, setting, ""}
hotkeylist[#hotkeylist + 1] = function(event) return hkcbfunc(cheat, event) end
end
end
menu[#menu + 1] = {"---", "", ""}
menu[#menu + 1] = {_("Done"), "", ""}
return menu
if poller then
return poller:overlay(menu)
else
return menu
end
end
for num, cheat in ipairs(cheats) do
menu[num] = {}
@ -703,7 +701,10 @@ function cheat.startplugin()
local function menu_callback(index, event)
manager.machine:popmessage()
if hotkeymenu then
if event == "select" or event == "clear" then
if event == "cancel" then
hotkeymenu = false
return true
else
index = index - 3
if index >= 1 and index <= #hotkeylist then
hotkeylist[index](event)
@ -712,9 +713,6 @@ function cheat.startplugin()
hotkeymenu = false
return true
end
elseif event == "cancel" then
hotkeymenu = false
return true
end
return false
end

214
plugins/commonui/init.lua Normal file
View File

@ -0,0 +1,214 @@
-- license:BSD-3-Clause
-- copyright-holders:Vas Crabb
local exports = {
name = 'commonui',
version = '0.0.1',
description = 'Common plugin UI helpers',
license = 'BSD-3-Clause',
author = { name = 'Vas Crabb' } }
local commonui = exports
function commonui.input_selection_menu(action, title, filter)
menu = { }
local choices
local index_first_choice
local index_cancel
local function populate_choices()
local ioport = manager.machine.ioport
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
choices = { }
for tag, port in pairs(manager.machine.ioport.ports) do
for name, field in pairs(port.fields) do
if (not filter) or filter(field) then
table.insert(choices, field)
end
end
end
table.sort(choices, compare)
local index = 1
local prev
while index <= #choices do
local current = choices[index]
if (not prev) or (prev.device.tag ~= current.device.tag) then
table.insert(choices, index, false)
index = index + 2
else
index = index + 1
end
prev = current
end
end
function menu:populate(initial_selection)
if not choices then
populate_choices()
end
local items = { }
if title then
table.insert(items, { title, '', 'off' })
table.insert(items, { '---', '', '' })
end
index_first_choice = #items + 1
local selection = index_first_choice
for index, field in ipairs(choices) do
if field then
table.insert(items, { _p('input-name', field.name), '', '' })
if initial_selection and (field.port.tag == initial_selection.port.tag) and (field.mask == initial_selection.mask) and (field.type == initial_selection.type) then
selection = #items
initial_selection = nil
end
else
local device = choices[index + 1].device
if device.owner then
table.insert(items, { string.format(_p('plugin-commonui', '%s [root%s]'), device.name, device.tag), '', 'heading' })
else
table.insert(items, { string.format(_p('plugin-commonui', '[root%s]'), device.tag), '', 'heading' })
end
end
end
table.insert(items, { '---', '', '' })
table.insert(items, { _p('plugin-commonui', 'Cancel'), '', '' })
index_cancel = #items
return items, selection
end
function menu:handle(index, event)
local selection
if (event == 'cancel') or ((index == input_item_cancel) and (event == 'select')) then
action(nil)
return true
elseif event == 'select' then
local field = choices[index - index_first_choice + 1]
if field then
action(field)
return true
end
elseif event == 'prevgroup' then
local found_break = false
while (index > index_first_choice) and (not selection) do
index = index - 1
if not choices[index - index_first_choice + 1] then
if found_break then
selection = index + 1
else
found_break = true
end
end
end
elseif event == 'nextgroup' then
while ((index - index_first_choice + 2) < #choices) and (not selection) do
index = index + 1
if not choices[index - index_first_choice + 1] then
selection = index + 1
end
end
end
return false, selection
end
return menu
end
function commonui.switch_polling_helper(starting_sequence)
helper = { }
local machine = manager.machine
local cancel = machine.ioport:token_to_input_type('UI_CANCEL')
local cancel_prompt = manager.ui:get_general_input_setting(cancel)
local input = machine.input
local uiinput = machine.uiinput
local poller = input:switch_sequence_poller()
local modified_ticks = 0
if starting_sequence then
poller:start(starting_sequence)
else
poller:start()
end
function helper:overlay(items, selection, flags)
if flags then
flags = flags .. " nokeys"
else
flags = "nokeys"
end
return items, selection, flags
end
function helper:poll()
-- prevent race condition between uiinput:pressed() and poll()
if (modified_ticks == 0) and poller.modified then
modified_ticks = emu.osd_ticks()
end
if uiinput:pressed(cancel) then
-- UI_CANCEL pressed, abort
machine:popmessage()
if (not poller.modified) or (modified_ticks == emu.osd_ticks()) then
-- cancelled immediately
self.sequence = nil
return true -- TODO: communicate this better?
else
-- entered something before cancelling
self.sequence = nil
return true
end
elseif poller:poll() then
if poller.valid then
-- valid sequence entered
machine:popmessage()
self.sequence = poller.sequence
return true
else
-- invalid sequence entered
machine:popmessage(_p('plugin-commonui', 'Invalid sequence entered'))
self.sequence = nil
return true
end
else
machine:popmessage(string.format(
_p('plugin-commonui', 'Enter sequence or press %s to cancel\n%s'),
cancel_prompt,
input:seq_name(poller.sequence)))
return false
end
end
return helper
end
return exports

View File

@ -0,0 +1,9 @@
{
"plugin": {
"name": "commonui",
"description": "Common plugin UI helpers",
"version": "0.0.1",
"author": "Vas Crabb",
"type": "library"
}
}

View File

@ -19,7 +19,7 @@ function dat.check(set, softlist)
if not status or not info then
return nil
end
return _("Command")
return _p("plugin-data", "Command")
end
function dat.get()

View File

@ -13,7 +13,7 @@ function dat.check(set, softlist)
if not status or not info then
return nil
end
return _("Gameinit")
return _p("plugin-data", "Gameinit")
end
function dat.get()

View File

@ -1217,7 +1217,7 @@ function dat.check(set, softlist)
if curset == set then
if output then
return _("High Scores")
return _p("plugin-data", "High Scores")
else
return nil
end
@ -1265,7 +1265,7 @@ function dat.check(set, softlist)
end
end
if output then
return _("High Scores")
return _p("plugin-data", "High Scores")
else
return nil
end

View File

@ -169,7 +169,7 @@ function dat.check(set, softlist)
info = stmt:get_value(0)
end
stmt:finalize()
return info and _("History") or nil
return info and _p("plugin-data", "History") or nil
end
function dat.get()

View File

@ -15,9 +15,9 @@ function dat.check(set, softlist)
local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$")
status, drvinfo = pcall(datread, "drv", "info", sourcefile)
if drvinfo then
info = info .. _("\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
info = info .. _p("plugin-data", "\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
end
return _("MAMEinfo")
return _p("plugin-data", "MAMEinfo")
end
function dat.get()

View File

@ -133,7 +133,7 @@ function dat.check(set, softlist)
info = "#j2\n" .. stmt:get_value(0)
end
stmt:finalize()
return info and _("MARPScore") or nil
return info and _p("plugin-data", "MARPScore") or nil
end
function dat.get()

View File

@ -16,9 +16,9 @@ function dat.check(set, softlist)
local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$")
status, drvinfo = pcall(datread, "drv", "info", sourcefile)
if drvinfo then
info = info .. _("\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
info = info .. _p("plugin-data", "\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
end
return _("MESSinfo")
return _p("plugin-data", "MESSinfo")
end
function dat.get()

View File

@ -19,7 +19,7 @@ function dat.check(set, softlist)
end
end
info = "#j2\n" .. table.concat(lines, "\n")
return _("Mamescore")
return _p("plugin-data", "Mamescore")
end
function dat.get()

View File

@ -12,7 +12,7 @@ function dat.check(set, softlist)
if not status or not info then
return nil
end
return _("Sysinfo")
return _p("plugin-data", "Sysinfo")
end
function dat.get()

View File

@ -127,7 +127,7 @@ function inputmacro.startplugin()
emu.register_frame_done(process_frame)
emu.register_start(start)
emu.register_stop(stop)
emu.register_menu(menu_callback, menu_populate, _('Input Macros'))
emu.register_menu(menu_callback, menu_populate, _p('plugin-inputmacro', 'Input Macros'))
end
return exports

View File

@ -9,6 +9,7 @@ local MENU_TYPES = { MACROS = 0, ADD = 1, EDIT = 2, INPUT = 3 }
-- Globals
local commonui
local macros
local menu_stack
@ -51,118 +52,43 @@ end
-- Input menu
local input_action
local input_menu
local input_start_field
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
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
return true
end
end
return false
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
function handle_input(index, action)
return input_menu:handle(index, action)
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
table.insert(input_choices, field)
end
end
end
table.sort(input_choices, compare)
local index = 1
local prev
while index <= #input_choices do
local current = input_choices[index]
if (not prev) or (prev.device.tag ~= current.device.tag) then
table.insert(input_choices, index, false)
index = index + 2
else
index = index + 1
end
prev = current
end
end
input_item_first_choice = #items + 1
local selection = input_item_first_choice
for index, field in ipairs(input_choices) do
if field then
items[#items + 1] = { _p('input-name', field.name), '', '' }
if input_start_field and (field.port.tag == input_start_field.port.tag) and (field.mask == input_start_field.mask) and (field.type == input_start_field.type) then
selection = #items
input_start_field = nil
end
else
local device = input_choices[index + 1].device
if device.owner then
items[#items + 1] = { string.format(_('plugin-inputmacro', '%s [root%s]'), device.name, device.tag), '', 'heading' }
else
items[#items + 1] = { string.format(_('plugin-inputmacro', '[root%s]'), device.tag), '', 'heading' }
end
end
end
input_start_field = nil
items[#items + 1] = { '---', '', '' }
items[#items + 1] = { _p('plugin-inputmacro', 'Cancel'), '', '' }
input_item_cancel = #items
return items, selection
return input_menu:populate(input_start_field)
end
@ -176,6 +102,7 @@ local edit_insert_position
local edit_name_buffer
local edit_items
local edit_item_exit
local edit_switch_poller
local function current_macro_complete()
if not edit_current_macro.binding then
@ -188,36 +115,18 @@ local function current_macro_complete()
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)
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
end
edit_switch_poller = nil
return true
end
return false
end
local command = edit_items[index]
local namecancel = false
@ -275,7 +184,11 @@ local function handle_edit_items(index, event)
end
elseif command.action == 'binding' then
if event == 'select' then
return set_binding()
if not commonui then
commonui = require('commonui')
end
edit_switch_poller = commonui.switch_polling_helper()
return true
end
elseif command.action == 'releaseaction' then
if (event == 'select') or (event == 'left') or (event == 'right') then
@ -320,14 +233,13 @@ local function handle_edit_items(index, event)
elseif command.action == 'input' then
local inputs = edit_current_macro.steps[command.step].inputs
if event == 'select' then
input_action =
local hanlder =
function(field)
inputs[command.input].port = field.port
inputs[command.input].field = field
end
input_start_field = inputs[command.input].field
start_input_menu(hanlder, inputs[command.input].field)
edit_start_selection = index
table.insert(menu_stack, MENU_TYPES.INPUT)
return true
elseif event == 'clear' then
if #inputs > 1 then
@ -338,14 +250,14 @@ local function handle_edit_items(index, event)
elseif command.action == 'addinput' then
if event == 'select' then
local inputs = edit_current_macro.steps[command.step].inputs
input_action =
local handler =
function(field)
inputs[#inputs + 1] = {
port = field.port,
field = field }
end
start_input_menu(handler)
edit_start_selection = index
table.insert(menu_stack, MENU_TYPES.INPUT)
return true
end
elseif command.action == 'deletestep' then
@ -368,7 +280,7 @@ local function handle_edit_items(index, event)
elseif command.action == 'addstep' then
if event == 'select' then
local steps = edit_current_macro.steps
input_action =
local handler =
function(field)
local newstep = {
inputs = {
@ -384,8 +296,8 @@ local function handle_edit_items(index, event)
edit_start_step = edit_insert_position
edit_insert_position = edit_insert_position + 1
end
start_input_menu(handler)
edit_start_selection = index
table.insert(menu_stack, MENU_TYPES.INPUT)
return true
elseif event == 'left' then
edit_insert_position = edit_insert_position - 1
@ -413,7 +325,7 @@ local function add_edit_items(items)
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, '' }
items[#items + 1] = { _p('plugin-inputmacro', 'Activation sequence'), activation, edit_switch_poller and 'lr' or '' }
edit_items[#items] = { action = 'binding' }
local releaseaction = edit_current_macro.earlycancel and _p('plugin-inputmacro', 'Stop immediately') or _p('plugin-inputmacro', 'Complete macro')
@ -484,7 +396,6 @@ local function handle_add(index, event)
if handle_edit_items(index, event) then
return true
elseif event == 'cancel' then
input_choices = nil
edit_current_macro = nil
edit_menu_active = false
edit_items = nil
@ -495,7 +406,6 @@ local function handle_add(index, event)
table.insert(macros, edit_current_macro)
macros_start_macro = #macros
end
input_choices = nil
edit_menu_active = false
edit_current_macro = nil
edit_items = nil
@ -509,7 +419,6 @@ 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
input_choices = nil
edit_current_macro = nil
edit_menu_active = false
edit_items = nil
@ -537,7 +446,11 @@ local function populate_add()
local selection = edit_start_selection
edit_start_selection = nil
return items, selection, 'lrrepeat'
if edit_switch_poller then
return edit_switch_poller:overlay(items, selection, 'lrrepeat')
else
return items, selection, 'lrrepeat'
end
end
local function populate_edit()
@ -554,7 +467,11 @@ local function populate_edit()
local selection = edit_start_selection
edit_start_selection = nil
return items, selection, 'lrrepeat'
if edit_switch_poller then
return edit_switch_poller:overlay(items, selection, 'lrrepeat')
else
return items, selection, 'lrrepeat'
end
end
@ -601,7 +518,7 @@ function populate_macros()
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] = { string.format(_p('plugin-inputmacro', 'Press %s to delete'), manager.ui:get_general_input_setting(ioport:token_to_input_type('UI_CLEAR'))), '', 'off' }
items[#items + 1] = { '---', '', '' }
macros_item_first_macro = #items + 1

View File

@ -86,7 +86,7 @@ function timer.startplugin()
local hrs = math.floor(time / 3600)
local min = math.floor((time % 3600) / 60)
local sec = time % 60
return string.format("%03d:%02d:%02d", hrs, min, sec)
return string.format(_p("plugin-timer", "%03d:%02d:%02d"), hrs, min, sec)
end
local lastupdate
@ -95,9 +95,9 @@ function timer.startplugin()
lastupdate = os.time()
local time = lastupdate - start_time
return
{{ _("Current time"), sectohms(time), "off" },
{ _("Total time"), sectohms(total_time + time), "off" },
{ _("Play Count"), play_count, "off" }},
{{ _p("plugin-timer", "Current time"), sectohms(time), "off" },
{ _p("plugin-timer", "Total time"), sectohms(total_time + time), "off" },
{ _p("plugin-timer", "Play Count"), play_count, "off" }},
nil,
"idle"
end
@ -106,7 +106,7 @@ function timer.startplugin()
return os.time() > lastupdate
end
emu.register_menu(menu_callback, menu_populate, _("Timer"))
emu.register_menu(menu_callback, menu_populate, _p("plugin-timer", "Timer"))
end
return exports

View File

@ -89,6 +89,8 @@ end
MAME_DIR .. "src/lib/util/ioprocsvec.h",
MAME_DIR .. "src/lib/util/jedparse.cpp",
MAME_DIR .. "src/lib/util/jedparse.h",
MAME_DIR .. "src/lib/util/language.cpp",
MAME_DIR .. "src/lib/util/language.h",
MAME_DIR .. "src/lib/util/lrucache.h",
MAME_DIR .. "src/lib/util/md5.cpp",
MAME_DIR .. "src/lib/util/md5.h",

View File

@ -171,6 +171,8 @@ files {
MAME_DIR .. "src/frontend/mame/ui/tapectrl.h",
MAME_DIR .. "src/frontend/mame/ui/text.cpp",
MAME_DIR .. "src/frontend/mame/ui/text.h",
MAME_DIR .. "src/frontend/mame/ui/textbox.cpp",
MAME_DIR .. "src/frontend/mame/ui/textbox.h",
MAME_DIR .. "src/frontend/mame/ui/toolbar.ipp",
MAME_DIR .. "src/frontend/mame/ui/ui.cpp",
MAME_DIR .. "src/frontend/mame/ui/ui.h",

View File

@ -8,6 +8,8 @@
***************************************************************************/
#include "util/language.h"
/***************************************************************************
BUILT-IN CORE MAPPINGS
@ -15,12 +17,6 @@
namespace {
// can't get frontend/mame/language.h from here
#ifndef N_p
#define N_p(ctx, msg) (msg)
#endif
#define CORE_INPUT_TYPES_P1 \
CORE_INPUT_TYPES_BEGIN(p1) \
INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, JOYSTICK_UP, N_p("input-name", "P1 Up"), input_seq(KEYCODE_UP, input_seq::or_code, JOYCODE_Y_UP_SWITCH_INDEXED(0)) ) \

View File

@ -51,6 +51,7 @@
#include "ui/uimain.h"
#include "util/ioprocsfilter.h"
#include "util/language.h"
#include "util/path.h"
#include "util/xmlfile.h"
@ -1693,6 +1694,8 @@ void render_target::load_layout_files(util::xml::data_node const &rootnode, bool
void render_target::load_additional_layout_files(const char *basename, bool have_artwork)
{
using util::lang_translate;
m_external_artwork = false;
// if override_artwork defined, load that and skip artwork other than default
@ -1841,7 +1844,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
viewnode->set_attribute(
"name",
util::string_format(
"Screen %1$u Standard (%2$u:%3$u)",
_("view-name", "Screen %1$u Standard (%2$u:%3$u)"),
i, screens[i].physical_x(), screens[i].physical_y()).c_str());
util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr));
if (!screennode)
@ -1867,7 +1870,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
viewnode->set_attribute(
"name",
util::string_format(
"Screen %1$u Pixel Aspect (%2$u:%3$u)",
_("view-name", "Screen %1$u Pixel Aspect (%2$u:%3$u)"),
i, screens[i].native_x(), screens[i].native_y()).c_str());
util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr));
if (!screennode)
@ -1889,7 +1892,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr));
if (!viewnode)
throw emu_fatalerror("Couldn't create XML node??");
viewnode->set_attribute("name", "Cocktail");
viewnode->set_attribute("name", _("view-name", "Cocktail"));
util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr));
if (!mirrornode)
@ -2009,10 +2012,10 @@ void render_target::load_additional_layout_files(const char *basename, bool have
};
// generate linear views
generate_view("Left-to-Right", screens.size(), false, [] (unsigned x, unsigned y) { return x; });
generate_view("Left-to-Right (Gapless)", screens.size(), true, [] (unsigned x, unsigned y) { return x; });
generate_view("Top-to-Bottom", 1U, false, [] (unsigned x, unsigned y) { return y; });
generate_view("Top-to-Bottom (Gapless)", 1U, true, [] (unsigned x, unsigned y) { return y; });
generate_view(_("view-name", "Left-to-Right"), screens.size(), false, [] (unsigned x, unsigned y) { return x; });
generate_view(_("view-name", "Left-to-Right (Gapless)"), screens.size(), true, [] (unsigned x, unsigned y) { return x; });
generate_view(_("view-name", "Top-to-Bottom"), 1U, false, [] (unsigned x, unsigned y) { return y; });
generate_view(_("view-name", "Top-to-Bottom (Gapless)"), 1U, true, [] (unsigned x, unsigned y) { return y; });
// generate fake cocktail view for systems with two screens
if (screens.size() == 2U)
@ -2024,7 +2027,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr));
if (!viewnode)
throw emu_fatalerror("Couldn't create XML node??");
viewnode->set_attribute("name", "Cocktail");
viewnode->set_attribute("name", _("view-name", "Cocktail"));
util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr));
if (!mirrornode)
@ -2063,7 +2066,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
if (!remainder || (((majdim + 1) / 2) <= remainder))
{
generate_view(
util::string_format("%1$u\xC3\x97%2$u Left-to-Right, Top-to-Bottom", majdim, mindim).c_str(),
util::string_format(_("view-name", u8"%1$u×%2$u Left-to-Right, Top-to-Bottom"), majdim, mindim).c_str(),
majdim,
false,
[&screens, majdim] (unsigned x, unsigned y)
@ -2072,7 +2075,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
return (screens.size() > i) ? int(i) : -1;
});
generate_view(
util::string_format("%1$u\xC3\x97%2$u Left-to-Right, Top-to-Bottom (Gapless)", majdim, mindim).c_str(),
util::string_format(_("view-name", u8"%1$u×%2$u Left-to-Right, Top-to-Bottom (Gapless)"), majdim, mindim).c_str(),
majdim,
true,
[&screens, majdim] (unsigned x, unsigned y)
@ -2081,7 +2084,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
return (screens.size() > i) ? int(i) : -1;
});
generate_view(
util::string_format("%1$u\xC3\x97%2$u Top-to-Bottom, Left-to-Right", mindim, majdim).c_str(),
util::string_format(_("view-name", u8"%1$u×%2$u Top-to-Bottom, Left-to-Right"), mindim, majdim).c_str(),
mindim,
false,
[&screens, majdim] (unsigned x, unsigned y)
@ -2090,7 +2093,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have
return (screens.size() > i) ? int(i) : -1;
});
generate_view(
util::string_format("%1$u\xC3\x97%2$u Top-to-Bottom, Left-to-Right (Gapless)", mindim, majdim).c_str(),
util::string_format(_("view-name", u8"%1$u×%2$u Top-to-Bottom, Left-to-Right (Gapless)"), mindim, majdim).c_str(),
mindim,
true,
[&screens, majdim] (unsigned x, unsigned y)

View File

@ -81,6 +81,7 @@
namespace {
//**************************************************************************
// COMMAND-LINE OPTIONS
//**************************************************************************

View File

@ -30,6 +30,7 @@
#include <cctype>
#include <cstring>
#include <future>
#include <locale>
#include <queue>
#include <type_traits>
#include <unordered_set>
@ -100,6 +101,7 @@ private:
typedef std::set<std::add_pointer_t<device_type>, device_type_compare> device_type_set;
std::string normalize_string(const char *string);
std::string normalize_string(std::string_view string);
// internal helper
void output_header(std::ostream &out, bool dtd);
@ -440,6 +442,11 @@ void info_xml_creator::output(std::ostream &out, const std::function<bool(const
device_type_set m_dev_set;
};
// TODO: maybe not the best place for this as it affects the stream passed in
// if the device part is threaded, the local streams used by the tasks can be
// imbued and the stream passed in can be left alone
out.imbue(std::locale::classic());
// prepare a driver enumerator and the queue
driver_enumerator drivlist(m_lookup_options);
device_filter devfilter(filter);
@ -491,6 +498,7 @@ void info_xml_creator::output(std::ostream &out, const std::function<bool(const
{
prepared_info result;
std::ostringstream stream;
stream.imbue(std::locale::classic());
// output each of the drivers
for (const game_driver &driver : drivers)
@ -568,26 +576,30 @@ namespace
std::string normalize_string(const char *string)
{
std::ostringstream stream;
if (string)
return normalize_string(std::string_view(string));
else
return std::string();
}
if (string != nullptr)
std::string normalize_string(std::string_view string)
{
std::string result;
result.reserve(string.length());
for (char ch : string)
{
while (*string)
switch (ch)
{
switch (*string)
{
case '\"': stream << "&quot;"; break;
case '&': stream << "&amp;"; break;
case '<': stream << "&lt;"; break;
case '>': stream << "&gt;"; break;
default:
stream << *string;
break;
}
++string;
case '\"': result.append("&quot;"); break;
case '&': result.append("&amp;"); break;
case '<': result.append("&lt;"); break;
case '>': result.append("&gt;"); break;
default: result.append(1, ch); break;
}
}
return stream.str();
return result;
}
@ -647,7 +659,8 @@ void output_header(std::ostream &out, bool dtd)
}
// top-level tag
out << util::string_format("<%s build=\"%s\" debug=\""
util::stream_format(out,
"<%s build=\"%s\" debug=\""
#ifdef MAME_DEBUG
"yes"
#else
@ -667,7 +680,7 @@ void output_header(std::ostream &out, bool dtd)
void output_footer(std::ostream &out)
{
// close the top level tag
out << util::string_format("</%s>\n", XML_ROOT);
util::stream_format(out, "</%s>\n", XML_ROOT);
}
@ -726,14 +739,14 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
}
// print the header and the machine name
out << util::string_format("\t<%s name=\"%s\"", XML_TOP, normalize_string(driver.name));
util::stream_format(out, "\t<%s name=\"%s\"", XML_TOP, normalize_string(driver.name));
// strip away any path information from the source_file and output it
const char *start = strrchr(driver.type.source(), '/');
if (!start)
start = strrchr(driver.type.source(), '\\');
start = start ? (start + 1) : driver.type.source();
out << util::string_format(" sourcefile=\"%s\"", normalize_string(start));
util::stream_format(out, " sourcefile=\"%s\"", normalize_string(start));
// append bios and runnable flags
if (driver.flags & machine_flags::IS_BIOS_ROOT)
@ -744,9 +757,9 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
// display clone information
int clone_of = drivlist.find(driver.parent);
if (clone_of != -1 && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT))
out << util::string_format(" cloneof=\"%s\"", normalize_string(drivlist.driver(clone_of).name));
util::stream_format(out, " cloneof=\"%s\"", normalize_string(drivlist.driver(clone_of).name));
if (clone_of != -1)
out << util::string_format(" romof=\"%s\"", normalize_string(drivlist.driver(clone_of).name));
util::stream_format(out, " romof=\"%s\"", normalize_string(drivlist.driver(clone_of).name));
// display sample information and close the game tag
output_sampleof(out, config.root_device());
@ -754,15 +767,15 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
// output game description
if (driver.type.fullname() != nullptr)
out << util::string_format("\t\t<description>%s</description>\n", normalize_string(driver.type.fullname()));
util::stream_format(out, "\t\t<description>%s</description>\n", normalize_string(driver.type.fullname()));
// print the year only if is a number or another allowed character (? or +)
if (driver.year != nullptr && strspn(driver.year, "0123456789?+") == strlen(driver.year))
out << util::string_format("\t\t<year>%s</year>\n", normalize_string(driver.year));
if (driver.year && strspn(driver.year, "0123456789?+") == strlen(driver.year))
util::stream_format(out, "\t\t<year>%s</year>\n", normalize_string(driver.year));
// print the manufacturer information
if (driver.manufacturer != nullptr)
out << util::string_format("\t\t<manufacturer>%s</manufacturer>\n", normalize_string(driver.manufacturer));
util::stream_format(out, "\t\t<manufacturer>%s</manufacturer>\n", normalize_string(driver.manufacturer));
// now print various additional information
output_bios(out, config.root_device());
@ -785,7 +798,7 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive
output_ramoptions(out, config.root_device());
// close the topmost tag
out << util::string_format("\t</%s>\n", XML_TOP);
util::stream_format(out, "\t</%s>\n", XML_TOP);
}
@ -824,15 +837,16 @@ void output_one_device(std::ostream &out, machine_config &config, device_t &devi
}
// start to output info
out << util::string_format("\t<%s name=\"%s\"", XML_TOP, normalize_string(device.shortname()));
util::stream_format(out, "\t<%s name=\"%s\"", XML_TOP, normalize_string(device.shortname()));
std::string src(device.source());
strreplace(src,"../", "");
out << util::string_format(" sourcefile=\"%s\" isdevice=\"yes\" runnable=\"no\"", normalize_string(src.c_str()));
util::stream_format(out, " sourcefile=\"%s\" isdevice=\"yes\" runnable=\"no\"", normalize_string(src));
auto const parent(device.type().parent_rom_device_type());
if (parent)
out << util::string_format(" romof=\"%s\"", normalize_string(parent->shortname()));
util::stream_format(out, " romof=\"%s\"", normalize_string(parent->shortname()));
output_sampleof(out, device);
out << ">\n" << util::string_format("\t\t<description>%s</description>\n", normalize_string(device.name()));
out << ">\n";
util::stream_format(out, "\t\t<description>%s</description>\n", normalize_string(device.name()));
output_bios(out, device);
output_rom(out, config, nullptr, nullptr, device);
@ -854,7 +868,7 @@ void output_one_device(std::ostream &out, machine_config &config, device_t &devi
output_images(out, device, devtag);
output_slots(out, config, device, devtag, nullptr);
output_software_lists(out, device, devtag);
out << util::string_format("\t</%s>\n", XML_TOP);
util::stream_format(out, "\t</%s>\n", XML_TOP);
}
@ -909,7 +923,7 @@ void output_device_refs(std::ostream &out, device_t &root)
{
for (device_t &device : device_enumerator(root))
if (&device != &root)
out << util::string_format("\t\t<device_ref name=\"%s\"/>\n", normalize_string(device.shortname()));
util::stream_format(out, "\t\t<device_ref name=\"%s\"/>\n", normalize_string(device.shortname()));
}
@ -926,7 +940,7 @@ void output_sampleof(std::ostream &out, device_t &device)
samples_iterator sampiter(samples);
if (sampiter.altbasename() != nullptr)
{
out << util::string_format(" sampleof=\"%s\"", normalize_string(sampiter.altbasename()));
util::stream_format(out, " sampleof=\"%s\"", normalize_string(sampiter.altbasename()));
// must stop here, as there can only be one attribute of the same name
return;
@ -954,8 +968,8 @@ void output_bios(std::ostream &out, device_t const &device)
{
// output extracted name and descriptions'
out << "\t\t<biosset";
out << util::string_format(" name=\"%s\"", normalize_string(bios.get_name()));
out << util::string_format(" description=\"%s\"", normalize_string(bios.get_description()));
util::stream_format(out, " name=\"%s\"", normalize_string(bios.get_name()));
util::stream_format(out, " description=\"%s\"", normalize_string(bios.get_description()));
if (defaultname && !std::strcmp(defaultname, bios.get_name()))
out << " default=\"yes\"";
out << "/>\n";
@ -1062,13 +1076,13 @@ void output_rom(std::ostream &out, machine_config &config, driver_enumerator *dr
// add name, merge, bios, and size tags
char const *const name(rom->name);
if (name && name[0])
out << util::string_format(" name=\"%s\"", normalize_string(name));
util::stream_format(out, " name=\"%s\"", normalize_string(name));
if (merge_name)
out << util::string_format(" merge=\"%s\"", normalize_string(merge_name));
util::stream_format(out, " merge=\"%s\"", normalize_string(merge_name));
if (bios_name)
out << util::string_format(" bios=\"%s\"", normalize_string(bios_name));
util::stream_format(out, " bios=\"%s\"", normalize_string(bios_name));
if (!is_disk)
out << util::string_format(" size=\"%u\"", rom_file_size(rom));
util::stream_format(out, " size=\"%u\"", rom_file_size(rom));
// dump checksum information only if there is a known dump
if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP))
@ -1077,17 +1091,17 @@ void output_rom(std::ostream &out, machine_config &config, driver_enumerator *dr
out << " status=\"nodump\"";
// append a region name
out << util::string_format(" region=\"%s\"", region->name);
util::stream_format(out, " region=\"%s\"", region->name);
if (!is_disk)
{
// for non-disk entries, print offset
out << util::string_format(" offset=\"%x\"", ROM_GETOFFSET(rom));
util::stream_format(out, " offset=\"%x\"", ROM_GETOFFSET(rom));
}
else
{
// for disk entries, add the disk index
out << util::string_format(" index=\"%x\" writable=\"%s\"", DISK_GETINDEX(rom), DISK_ISREADONLY(rom) ? "no" : "yes");
util::stream_format(out, " index=\"%x\" writable=\"%s\"", DISK_GETINDEX(rom), DISK_ISREADONLY(rom) ? "no" : "yes");
}
// add optional flag
@ -1120,7 +1134,7 @@ void output_sample(std::ostream &out, device_t &device)
continue;
// output the sample name
out << util::string_format("\t\t<sample name=\"%s\"/>\n", normalize_string(samplename));
util::stream_format(out, "\t\t<sample name=\"%s\"/>\n", normalize_string(samplename));
}
}
}
@ -1143,9 +1157,9 @@ void output_chips(std::ostream &out, device_t &device, const char *root_tag)
out << "\t\t<chip";
out << " type=\"cpu\"";
out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str()));
out << util::string_format(" name=\"%s\"", normalize_string(exec.device().name()));
out << util::string_format(" clock=\"%d\"", exec.device().clock());
util::stream_format(out, " tag=\"%s\"", normalize_string(newtag));
util::stream_format(out, " name=\"%s\"", normalize_string(exec.device().name()));
util::stream_format(out, " clock=\"%d\"", exec.device().clock());
out << "/>\n";
}
}
@ -1160,10 +1174,10 @@ void output_chips(std::ostream &out, device_t &device, const char *root_tag)
out << "\t\t<chip";
out << " type=\"audio\"";
out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str()));
out << util::string_format(" name=\"%s\"", normalize_string(sound.device().name()));
util::stream_format(out, " tag=\"%s\"", normalize_string(newtag));
util::stream_format(out, " name=\"%s\"", normalize_string(sound.device().name()));
if (sound.device().clock() != 0)
out << util::string_format(" clock=\"%d\"", sound.device().clock());
util::stream_format(out, " clock=\"%d\"", sound.device().clock());
out << "/>\n";
}
}
@ -1185,7 +1199,7 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con
std::string newtag(screendev.tag()), oldtag(":");
newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
out << util::string_format("\t\t<display tag=\"%s\"", normalize_string(newtag.c_str()));
util::stream_format(out, "\t\t<display tag=\"%s\"", normalize_string(newtag));
switch (screendev.screen_type())
{
@ -1229,12 +1243,12 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con
if (screendev.screen_type() != SCREEN_TYPE_VECTOR)
{
const rectangle &visarea = screendev.visible_area();
out << util::string_format(" width=\"%d\"", visarea.width());
out << util::string_format(" height=\"%d\"", visarea.height());
util::stream_format(out, " width=\"%d\"", visarea.width());
util::stream_format(out, " height=\"%d\"", visarea.height());
}
// output refresh rate
out << util::string_format(" refresh=\"%f\"", ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds()));
util::stream_format(out, " refresh=\"%f\"", ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds()));
// output raw video parameters only for games that are not vector
// and had raw parameters specified
@ -1242,13 +1256,13 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con
{
int pixclock = screendev.width() * screendev.height() * ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds());
out << util::string_format(" pixclock=\"%d\"", pixclock);
out << util::string_format(" htotal=\"%d\"", screendev.width());
out << util::string_format(" hbend=\"%d\"", screendev.visible_area().min_x);
out << util::string_format(" hbstart=\"%d\"", screendev.visible_area().max_x+1);
out << util::string_format(" vtotal=\"%d\"", screendev.height());
out << util::string_format(" vbend=\"%d\"", screendev.visible_area().min_y);
out << util::string_format(" vbstart=\"%d\"", screendev.visible_area().max_y+1);
util::stream_format(out, " pixclock=\"%d\"", pixclock);
util::stream_format(out, " htotal=\"%d\"", screendev.width());
util::stream_format(out, " hbend=\"%d\"", screendev.visible_area().min_x);
util::stream_format(out, " hbstart=\"%d\"", screendev.visible_area().max_x+1);
util::stream_format(out, " vtotal=\"%d\"", screendev.height());
util::stream_format(out, " vbend=\"%d\"", screendev.visible_area().min_y);
util::stream_format(out, " vbstart=\"%d\"", screendev.visible_area().max_y+1);
}
out << " />\n";
}
@ -1271,7 +1285,7 @@ void output_sound(std::ostream &out, device_t &device)
if (snditer.first() == nullptr)
speakers = 0;
out << util::string_format("\t\t<sound channels=\"%d\"/>\n", speakers);
util::stream_format(out, "\t\t<sound channels=\"%d\"/>\n", speakers);
}
@ -1297,7 +1311,7 @@ void output_ioport_condition(std::ostream &out, const ioport_condition &conditio
case ioport_condition::NOTLESSTHAN: rel = "ge"; break;
}
out << util::string_format("<condition tag=\"%s\" mask=\"%u\" relation=\"%s\" value=\"%u\"/>\n", normalize_string(condition.tag()), condition.mask(), rel, condition.value());
util::stream_format(out, "<condition tag=\"%s\" mask=\"%u\" relation=\"%s\" value=\"%u\"/>\n", normalize_string(condition.tag()), condition.mask(), rel, condition.value());
}
//-------------------------------------------------
@ -1700,13 +1714,13 @@ void output_input(std::ostream &out, const ioport_list &portlist)
// Output the input info
// First basic info
out << "\t\t<input";
out << util::string_format(" players=\"%d\"", nplayer);
util::stream_format(out, " players=\"%d\"", nplayer);
if (ncoin != 0)
out << util::string_format(" coins=\"%d\"", ncoin);
util::stream_format(out, " coins=\"%d\"", ncoin);
if (service)
out << util::string_format(" service=\"yes\"");
util::stream_format(out, " service=\"yes\"");
if (tilt)
out << util::string_format(" tilt=\"yes\"");
util::stream_format(out, " tilt=\"yes\"");
out << ">\n";
// Then controller specific ones
@ -1716,21 +1730,21 @@ void output_input(std::ostream &out, const ioport_list &portlist)
//printf("type %s - player %d - buttons %d\n", elem.type, elem.player, elem.nbuttons);
if (elem.analog)
{
out << util::string_format("\t\t\t<control type=\"%s\"", normalize_string(elem.type));
util::stream_format(out, "\t\t\t<control type=\"%s\"", normalize_string(elem.type));
if (nplayer > 1)
out << util::string_format(" player=\"%d\"", elem.player);
util::stream_format(out, " player=\"%d\"", elem.player);
if (elem.nbuttons > 0)
{
out << util::string_format(" buttons=\"%d\"", strcmp(elem.type, "stick") ? elem.nbuttons : elem.maxbuttons);
util::stream_format(out, " buttons=\"%d\"", strcmp(elem.type, "stick") ? elem.nbuttons : elem.maxbuttons);
if (elem.reqbuttons < elem.nbuttons)
out << util::string_format(" reqbuttons=\"%d\"", elem.reqbuttons);
util::stream_format(out, " reqbuttons=\"%d\"", elem.reqbuttons);
}
if (elem.min != 0 || elem.max != 0)
out << util::string_format(" minimum=\"%d\" maximum=\"%d\"", elem.min, elem.max);
util::stream_format(out, " minimum=\"%d\" maximum=\"%d\"", elem.min, elem.max);
if (elem.sensitivity != 0)
out << util::string_format(" sensitivity=\"%d\"", elem.sensitivity);
util::stream_format(out, " sensitivity=\"%d\"", elem.sensitivity);
if (elem.keydelta != 0)
out << util::string_format(" keydelta=\"%d\"", elem.keydelta);
util::stream_format(out, " keydelta=\"%d\"", elem.keydelta);
if (elem.reverse)
out << " reverse=\"yes\"";
@ -1742,14 +1756,14 @@ void output_input(std::ostream &out, const ioport_list &portlist)
if (elem.helper[0] == 0 && elem.helper[1] != 0) { elem.helper[0] = elem.helper[1]; elem.helper[1] = 0; }
if (elem.helper[1] == 0 && elem.helper[2] != 0) { elem.helper[1] = elem.helper[2]; elem.helper[2] = 0; }
const char *joys = (elem.helper[2] != 0) ? "triple" : (elem.helper[1] != 0) ? "double" : "";
out << util::string_format("\t\t\t<control type=\"%s%s\"", joys, normalize_string(elem.type));
util::stream_format(out, "\t\t\t<control type=\"%s%s\"", joys, normalize_string(elem.type));
if (nplayer > 1)
out << util::string_format(" player=\"%d\"", elem.player);
util::stream_format(out, " player=\"%d\"", elem.player);
if (elem.nbuttons > 0)
{
out << util::string_format(" buttons=\"%d\"", strcmp(elem.type, "joy") ? elem.nbuttons : elem.maxbuttons);
util::stream_format(out, " buttons=\"%d\"", strcmp(elem.type, "joy") ? elem.nbuttons : elem.maxbuttons);
if (elem.reqbuttons < elem.nbuttons)
out << util::string_format(" reqbuttons=\"%d\"", elem.reqbuttons);
util::stream_format(out, " reqbuttons=\"%d\"", elem.reqbuttons);
}
for (int lp = 0; lp < 3 && elem.helper[lp] != 0; lp++)
{
@ -1759,7 +1773,7 @@ void output_input(std::ostream &out, const ioport_list &portlist)
switch (elem.helper[lp] & (DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT))
{
case DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT:
helper = string_format("%d", (elem.ways == 0) ? 8 : elem.ways);
helper = util::string_format(std::locale::classic(), "%d", (elem.ways == 0) ? 8 : elem.ways);
ways = helper.c_str();
break;
case DIR_LEFT | DIR_RIGHT:
@ -1784,7 +1798,7 @@ void output_input(std::ostream &out, const ioport_list &portlist)
ways = "strange2";
break;
}
out << util::string_format(" ways%s=\"%s\"", plural, ways);
util::stream_format(out, " ways%s=\"%s\"", plural, ways);
}
out << "/>\n";
}
@ -1811,15 +1825,15 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char
// output the switch name information
std::string const normalized_field_name(normalize_string(field.name()));
std::string const normalized_newtag(normalize_string(newtag.c_str()));
out << util::string_format("\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name.c_str(), normalized_newtag.c_str(), field.mask());
std::string const normalized_newtag(normalize_string(newtag));
util::stream_format(out, "\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name, normalized_newtag, field.mask());
if (!field.condition().none())
output_ioport_condition(out, field.condition(), 3);
// loop over locations
for (ioport_diplocation const &diploc : field.diplocations())
{
out << util::string_format("\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, normalize_string(diploc.name()), diploc.number());
util::stream_format(out, "\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, normalize_string(diploc.name()), diploc.number());
if (diploc.inverted())
out << " inverted=\"yes\"";
out << "/>\n";
@ -1828,7 +1842,7 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char
// loop over settings
for (ioport_setting const &setting : field.settings())
{
out << util::string_format("\t\t\t<%s name=\"%s\" value=\"%u\"", innertag, normalize_string(setting.name()), setting.value());
util::stream_format(out, "\t\t\t<%s name=\"%s\" value=\"%u\"", innertag, normalize_string(setting.name()), setting.value());
if (setting.value() == field.defvalue())
out << " default=\"yes\"";
if (setting.condition().none())
@ -1839,12 +1853,12 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char
{
out << ">\n";
output_ioport_condition(out, setting.condition(), 4);
out << util::string_format("\t\t\t</%s>\n", innertag);
util::stream_format(out, "\t\t\t</%s>\n", innertag);
}
}
// terminate the switch entry
out << util::string_format("\t\t</%s>\n", outertag);
util::stream_format(out, "\t\t</%s>\n", outertag);
}
}
@ -1857,13 +1871,13 @@ void output_ports(std::ostream &out, const ioport_list &portlist)
// cycle through ports
for (auto &port : portlist)
{
out << util::string_format("\t\t<port tag=\"%s\">\n", normalize_string(port.second->tag()));
util::stream_format(out, "\t\t<port tag=\"%s\">\n", normalize_string(port.second->tag()));
for (ioport_field const &field : port.second->fields())
{
if (field.is_analog())
out << util::string_format("\t\t\t<analog mask=\"%u\"/>\n", field.mask());
util::stream_format(out, "\t\t\t<analog mask=\"%u\"/>\n", field.mask());
}
out << util::string_format("\t\t</port>\n");
util::stream_format(out, "\t\t</port>\n");
}
}
@ -1880,7 +1894,7 @@ void output_adjusters(std::ostream &out, const ioport_list &portlist)
for (ioport_field const &field : port.second->fields())
if (field.type() == IPT_ADJUSTER)
{
out << util::string_format("\t\t<adjuster name=\"%s\" default=\"%d\"/>\n", normalize_string(field.name()), field.defvalue());
util::stream_format(out, "\t\t<adjuster name=\"%s\" default=\"%d\"/>\n", normalize_string(field.name()), field.defvalue());
}
}
@ -1955,7 +1969,7 @@ void output_features(std::ostream &out, device_type type, device_t::feature_type
{
if (flags & feature.first)
{
out << util::string_format("\t\t<feature type=\"%s\"", feature.second);
util::stream_format(out, "\t\t<feature type=\"%s\"", feature.second);
if (type.unemulated_features() & feature.first)
{
out << " status=\"unemulated\"";
@ -1991,11 +2005,11 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
// print m_output device type
out << util::string_format("\t\t<device type=\"%s\"", normalize_string(imagedev.image_type_name()));
util::stream_format(out, "\t\t<device type=\"%s\"", normalize_string(imagedev.image_type_name()));
// does this device have a tag?
if (imagedev.device().tag())
out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str()));
util::stream_format(out, " tag=\"%s\"", normalize_string(newtag));
// is this device available as media switch?
if (!loadable)
@ -2006,7 +2020,7 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
out << " mandatory=\"1\"";
if (imagedev.image_interface() && imagedev.image_interface()[0])
out << util::string_format(" interface=\"%s\"", normalize_string(imagedev.image_interface()));
util::stream_format(out, " interface=\"%s\"", normalize_string(imagedev.image_interface()));
// close the XML tag
out << ">\n";
@ -2017,8 +2031,8 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
char const *const shortname = imagedev.brief_instance_name().c_str();
out << "\t\t\t<instance";
out << util::string_format(" name=\"%s\"", normalize_string(name));
out << util::string_format(" briefname=\"%s\"", normalize_string(shortname));
util::stream_format(out, " name=\"%s\"", normalize_string(name));
util::stream_format(out, " briefname=\"%s\"", normalize_string(shortname));
out << "/>\n";
char const *extensions(imagedev.file_extensions());
@ -2027,7 +2041,7 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag)
char const *end(extensions);
while (*end && (',' != *end))
++end;
out << util::string_format("\t\t\t<extension name=\"%s\"/>\n", normalize_string(std::string(extensions, end).c_str()));
util::stream_format(out, "\t\t\t<extension name=\"%s\"/>\n", normalize_string(std::string_view(extensions, end - extensions)));
extensions = *end ? (end + 1) : nullptr;
}
}
@ -2056,7 +2070,7 @@ void output_slots(std::ostream &out, machine_config &config, device_t &device, c
// print m_output device type
if (listed)
out << util::string_format("\t\t<slot name=\"%s\">\n", normalize_string(newtag.c_str()));
util::stream_format(out, "\t\t<slot name=\"%s\">\n", normalize_string(newtag));
for (auto &option : slot.option_list())
{
@ -2071,9 +2085,9 @@ void output_slots(std::ostream &out, machine_config &config, device_t &device, c
if (listed && option.second->selectable())
{
out << util::string_format("\t\t\t<slotoption name=\"%s\"", normalize_string(option.second->name()));
out << util::string_format(" devname=\"%s\"", normalize_string(dev->shortname()));
if (slot.default_option() != nullptr && strcmp(slot.default_option(), option.second->name())==0)
util::stream_format(out, "\t\t\t<slotoption name=\"%s\"", normalize_string(option.second->name()));
util::stream_format(out, " devname=\"%s\"", normalize_string(dev->shortname()));
if (slot.default_option() && !strcmp(slot.default_option(), option.second->name()))
out << " default=\"yes\"";
out << "/>\n";
}
@ -2106,10 +2120,10 @@ void output_software_lists(std::ostream &out, device_t &root, const char *root_t
std::string newtag(swlist.tag()), oldtag(":");
newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
out << util::string_format("\t\t<softwarelist tag=\"%s\" name=\"%s\" status=\"%s\"", normalize_string(newtag.c_str()), normalize_string(swlist.list_name().c_str()), swlist.is_original() ? "original" : "compatible");
util::stream_format(out, "\t\t<softwarelist tag=\"%s\" name=\"%s\" status=\"%s\"", normalize_string(newtag), normalize_string(swlist.list_name()), swlist.is_original() ? "original" : "compatible");
if (swlist.filter())
out << util::string_format(" filter=\"%s\"", normalize_string(swlist.filter()));
util::stream_format(out, " filter=\"%s\"", normalize_string(swlist.filter()));
out << "/>\n";
}
}
@ -2135,15 +2149,15 @@ void output_ramoptions(std::ostream &out, device_t &root)
{
assert(!havedefault);
havedefault = true;
out << util::string_format("\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", normalize_string(option.first.c_str()), option.second);
util::stream_format(out, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", normalize_string(option.first), option.second);
}
else
{
out << util::string_format("\t\t<ramoption name=\"%s\">%u</ramoption>\n", normalize_string(option.first.c_str()), option.second);
util::stream_format(out, "\t\t<ramoption name=\"%s\">%u</ramoption>\n", normalize_string(option.first), option.second);
}
}
if (!havedefault)
out << util::string_format("\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", ram.default_size_string(), defsize);
util::stream_format(out, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", ram.default_size_string(), defsize);
break;
}
}

View File

@ -15,28 +15,12 @@
#include "corestr.h"
#include <cstring>
#include <memory>
#include <new>
#include <unordered_map>
#include <utility>
namespace {
constexpr u32 MO_MAGIC = 0x950412de;
constexpr u32 MO_MAGIC_REVERSED = 0xde120495;
std::unique_ptr<u32 []> f_translation_data;
std::unordered_map<std::string_view, std::pair<char const *, u32> > f_translation_map;
} // anonymous namespace
#include <string>
void load_translation(emu_options &m_options)
{
f_translation_data.reset();
f_translation_map.clear();
util::unload_translation();
std::string name = m_options.language();
if (name.empty())
@ -52,174 +36,6 @@ void load_translation(emu_options &m_options)
return;
}
u64 const size = file.size();
if (20 > size)
{
file.close();
osd_printf_error("Error reading translation file %s: %u-byte file is too small to contain translation data\n", name, size);
return;
}
f_translation_data.reset(new (std::nothrow) u32 [(size + 3) / 4]);
if (!f_translation_data)
{
file.close();
osd_printf_error("Failed to allocate %u bytes to load translation data file %s\n", size, name);
return;
}
auto const read = file.read(f_translation_data.get(), size);
file.close();
if (read != size)
{
osd_printf_error("Error reading translation file %s: requested %u bytes but got %u bytes\n", name, size, read);
f_translation_data.reset();
return;
}
if ((f_translation_data[0] != MO_MAGIC) && (f_translation_data[0] != MO_MAGIC_REVERSED))
{
osd_printf_error("Error reading translation file %s: unrecognized magic number 0x%08X\n", name, f_translation_data[0]);
f_translation_data.reset();
return;
}
auto fetch_word =
[reversed = f_translation_data[0] == MO_MAGIC_REVERSED, words = f_translation_data.get()] (size_t offset)
{
return reversed ? swapendian_int32(words[offset]) : words[offset];
};
// FIXME: check major/minor version number
if ((fetch_word(3) % 4) || (fetch_word(4) % 4))
{
osd_printf_error("Error reading translation file %s: table offsets %u and %u are not word-aligned\n", name, fetch_word(3), fetch_word(4));
f_translation_data.reset();
return;
}
u32 const number_of_strings = fetch_word(2);
u32 const original_table_offset = fetch_word(3) >> 2;
u32 const translation_table_offset = fetch_word(4) >> 2;
if ((4 * (original_table_offset + (u64(number_of_strings) * 2))) > size)
{
osd_printf_error("Error reading translation file %s: %u-entry original string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(3), size);
f_translation_data.reset();
return;
}
if ((4 * (translation_table_offset + (u64(number_of_strings) * 2))) > size)
{
osd_printf_error("Error reading translation file %s: %u-entry translated string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(4), size);
f_translation_data.reset();
return;
}
osd_printf_verbose("Reading translation file %s: %u strings, original table at word offset %u, translated table at word offset %u\n", name, number_of_strings, original_table_offset, translation_table_offset);
char const *const data = reinterpret_cast<char const *>(f_translation_data.get());
for (u32 i = 1; number_of_strings > i; ++i)
{
u32 const original_length = fetch_word(original_table_offset + (2 * i));
u32 const original_offset = fetch_word(original_table_offset + (2 * i) + 1);
if ((original_length + original_offset) >= size)
{
osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u extends past end of %u-byte file\n", name, original_length, i, original_offset, size);
continue;
}
if (data[original_length + original_offset])
{
osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u is not correctly NUL-terminated\n", name, original_length, i, original_offset);
continue;
}
u32 const translation_length = fetch_word(translation_table_offset + (2 * i));
u32 const translation_offset = fetch_word(translation_table_offset + (2 * i) + 1);
if ((translation_length + translation_offset) >= size)
{
osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u extends past end of %u-byte file\n", name, translation_length, i, translation_offset, size);
continue;
}
if (data[translation_length + translation_offset])
{
osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u is not correctly NUL-terminated\n", name, translation_length, i, translation_offset);
continue;
}
std::string_view const original(&data[original_offset], original_length);
char const *const translation(&data[translation_offset]);
auto const ins = f_translation_map.emplace(original, std::make_pair(translation, translation_length));
if (!ins.second)
{
osd_printf_warning(
"Loading translation file %s: translation %u '%s'='%s' conflicts with previous translation '%s'='%s'\n",
name,
i,
original,
translation,
ins.first->first,
ins.first->second.first);
}
}
osd_printf_verbose("Loaded %u translations from file %s\n", f_translation_map.size(), name);
}
char const *lang_translate(char const *message)
{
auto const found = f_translation_map.find(message);
if (f_translation_map.end() != found)
return found->second.first;
return message;
}
std::string_view lang_translate(std::string_view message)
{
auto const found = f_translation_map.find(message);
if (f_translation_map.end() != found)
return std::string_view(found->second.first, found->second.second);
return message;
}
char const *lang_translate(char const *context, char const *message)
{
if (!f_translation_map.empty())
{
auto const ctxlen(std::strlen(context));
auto const msglen(std::strlen(message));
std::string key;
key.reserve(ctxlen + 1 + msglen);
key.append(context, ctxlen);
key.append(1, '\004');
key.append(message, msglen);
auto const found = f_translation_map.find(key);
if (f_translation_map.end() != found)
return found->second.first;
}
return message;
}
std::string_view lang_translate(char const *context, std::string_view message)
{
return lang_translate(std::string_view(context), message);
}
std::string_view lang_translate(std::string_view context, std::string_view message)
{
if (!f_translation_map.empty())
{
std::string key;
key.reserve(context.length() + 1 + message.length());
key.append(context);
key.append(1, '\004');
key.append(message);
auto const found = f_translation_map.find(key);
if (f_translation_map.end() != found)
return std::string_view(found->second.first, found->second.second);
}
return message;
osd_printf_verbose("Loading translation file %s\n", file.fullpath());
util::load_translation(file);
}

View File

@ -12,25 +12,11 @@
#pragma once
#include <string_view>
#include "util/language.h"
//**************************************************************************
// LOCALIZATION SUPPORT
//**************************************************************************
void load_translation(emu_options &options);
#define _(...) lang_translate(__VA_ARGS__)
#define N_(msg) (msg)
#define N_p(ctx, msg) (msg)
void load_translation(emu_options &option);
char const *lang_translate(char const *message);
std::string_view lang_translate(std::string_view message);
char const *lang_translate(char const *context, char const *message);
std::string_view lang_translate(char const *context, std::string_view message);
std::string_view lang_translate(std::string_view context, std::string_view message);
using util::lang_translate;
#endif // MAME_FRONTEND_MAME_LANGUAGE_H

View File

@ -747,6 +747,8 @@ void lua_engine::initialize()
emu["print_error"] = [] (const char *str) { osd_printf_error("%s\n", str); };
emu["print_info"] = [] (const char *str) { osd_printf_info("%s\n", str); };
emu["print_debug"] = [] (const char *str) { osd_printf_debug("%s\n", str); };
emu["osd_ticks"] = &osd_ticks;
emu["osd_ticks_per_second"] = &osd_ticks_per_second;
emu["driver_find"] =
[] (sol::this_state s, const char *driver) -> sol::object
{
@ -1737,6 +1739,10 @@ void lua_engine::initialize()
ui_type["get_char_width"] = [] (mame_ui_manager &m, uint32_t utf8char) { return m.get_char_width(utf8char); };
ui_type["get_string_width"] = &mame_ui_manager::get_string_width;
ui_type["set_aggressive_input_focus"] = [](mame_ui_manager &m, bool aggressive_focus) { osd_set_aggressive_input_focus(aggressive_focus); };
ui_type["get_general_input_setting"] = sol::overload(
// TODO: overload with sequence type string - parser isn't available here
[] (mame_ui_manager &ui, ioport_type type, int player) { return ui.get_general_input_setting(type, player, SEQ_TYPE_STANDARD); },
[] (mame_ui_manager &ui, ioport_type type) { return ui.get_general_input_setting(type, 0, SEQ_TYPE_STANDARD); });
ui_type["options"] = sol::property([] (mame_ui_manager &m) { return static_cast<core_options *>(&m.options()); });
ui_type["line_height"] = sol::property(&mame_ui_manager::get_line_height);
ui_type["menu_active"] = sol::property(&mame_ui_manager::is_menu_active);

View File

@ -12,12 +12,9 @@
#include "ui/about.h"
#include "ui/ui.h"
#include "ui/utils.h"
#include "mame.h"
#include <string_view>
namespace ui {
@ -38,21 +35,22 @@ namespace {
//-------------------------------------------------
menu_about::menu_about(mame_ui_manager &mui, render_container &container)
: menu(mui, container)
: menu_textbox(mui, container)
, m_header{
util::string_format(
#ifdef MAME_DEBUG
_("%1$s %2$s (%3$s%4$sP%5$s, debug)"),
_("about-header", "%1$s %2$s (%3$s%4$sP%5$s, debug)"),
#else
_("%1$s %2$s (%3$s%4$sP%5$s)"),
_("about-header", "%1$s %2$s (%3$s%4$sP%5$s)"),
#endif
emulator_info::get_appname(),
bare_build_version,
(sizeof(int) == sizeof(void *)) ? "I" : "",
(sizeof(long) == sizeof(void *)) ? "L" : (sizeof(long long) == sizeof(void *)) ? "LL" : "",
sizeof(void *) * 8),
util::string_format(_("Revision: %1$s"), bare_vcs_revision) }
util::string_format(_("about-header", "Revision: %1$s"), bare_vcs_revision) }
{
set_process_flags(PROCESS_CUSTOM_NAV);
}
@ -81,136 +79,23 @@ void menu_about::custom_render(void *selectedref, float top, float bottom, float
//-------------------------------------------------
// draw - draw about
// populate_text - populate the about box text
//-------------------------------------------------
void menu_about::draw(uint32_t flags)
void menu_about::populate_text(std::optional<text_layout> &layout, float &width, int &lines)
{
rgb_t const color = ui().colors().text_color();
float const aspect = machine().render().ui_aspect(&container());
float const line_height = ui().get_line_height();
float const ud_arrow_width = line_height * aspect;
float const gutter_width = 0.52f * line_height * aspect;
float const visible_width = 1.0f - (2.0f * ui().box_lr_border() * aspect);
float const visible_left = (1.0f - visible_width) * 0.5f;
float const extra_height = 2.0f * line_height;
float const visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height;
// determine effective positions taking into account the hilighting arrows
float const maximum_width = visible_width - 2.0f * gutter_width;
draw_background();
map_mouse();
// account for extra space at the top and bottom
float visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height;
m_visible_lines = int(std::trunc(visible_main_menu_height / line_height));
visible_main_menu_height = float(m_visible_lines) * line_height;
// compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust
float const visible_top = ((1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f) + get_customtop();
// lay out the text if necessary
if (!m_layout || (m_layout->width() != maximum_width))
if (!layout || (layout->width() != width))
{
m_layout.emplace(ui().create_layout(container(), maximum_width));
rgb_t const color = ui().colors().text_color();
layout.emplace(ui().create_layout(container(), width));
for (char const *const *line = copying_text; *line; ++line)
{
m_layout->add_text(*line, color);
m_layout->add_text("\n", color);
layout->add_text(*line, color);
layout->add_text("\n", color);
}
lines = layout->lines();
}
float const actual_width = m_layout->actual_width();
// compute text box size
float const x1 = visible_left + ((maximum_width - actual_width) * 0.5f);
float const y1 = visible_top - ui().box_tb_border();
float const x2 = visible_left + visible_width - ((maximum_width - actual_width) * 0.5f);
float const y2 = visible_top + visible_main_menu_height + ui().box_tb_border() + extra_height;
float const effective_left = x1 + gutter_width;
float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH;
float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH;
float const separator = visible_top + float(m_visible_lines) * line_height;
ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color());
int const visible_items = m_layout->lines();
m_visible_lines = (std::min)(visible_items, m_visible_lines);
top_line = (std::max)(0, top_line);
if (top_line + m_visible_lines >= visible_items)
top_line = visible_items - m_visible_lines;
clear_hover();
if (top_line)
{
// if we're on the top line, display the up arrow
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, visible_top, line_x1, visible_top + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, visible_top,
line_x1, visible_top + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_UP);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, visible_top + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, visible_top + 0.75f * line_height,
fgcolor, ROT0);
}
if ((top_line + m_visible_lines) < visible_items)
{
// if we're on the bottom line, display the down arrow
float const line_y = visible_top + float(m_visible_lines - 1) * line_height;
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, line_y, line_x1, line_y + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, line_y,
line_x1, line_y + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_DOWN);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, line_y + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, line_y + 0.75f * line_height,
fgcolor, ROT0 ^ ORIENTATION_FLIP_Y);
}
// return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow
m_visible_items = m_visible_lines - (top_line ? 1 : 0) - (top_line + m_visible_lines != visible_items);
m_layout->emit(
container(),
top_line ? (top_line + 1) : 0, m_visible_items,
effective_left, visible_top + (top_line ? line_height : 0.0f));
// add visual separator before the "return to prevous menu" item
container().add_line(
x1, separator + (0.5f * line_height),
x2, separator + (0.5f * line_height),
UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
menu_item const &pitem = item(0);
std::string_view const itemtext = pitem.text;
float const line_y0 = separator + line_height;
float const line_y1 = line_y0 + line_height;
if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem))
set_hover(0);
highlight(line_x0, line_y0, line_x1, line_y1, ui().colors().selected_bg_color());
ui().draw_text_full(
container(), itemtext,
effective_left, line_y0, actual_width,
text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE,
mame_ui_manager::NORMAL,
ui().colors().selected_color(), ui().colors().selected_bg_color(),
nullptr, nullptr);
// if there is something special to add, do it by calling the virtual method
custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2);
width = layout->actual_width();
}
@ -229,38 +114,10 @@ void menu_about::populate(float &customtop, float &custombottom)
// handle - manages inputs in the about modal
//-------------------------------------------------
void menu_about::handle()
void menu_about::handle(event const *ev)
{
const event *event = process(PROCESS_CUSTOM_NAV);
if (event)
{
switch (event->iptkey)
{
case IPT_UI_UP:
--top_line;
break;
case IPT_UI_DOWN:
++top_line;
break;
case IPT_UI_PAGE_UP:
top_line -= m_visible_lines - 3;
break;
case IPT_UI_PAGE_DOWN:
top_line += m_visible_lines - 3;
break;
case IPT_UI_HOME:
top_line = 0;
break;
case IPT_UI_END:
top_line = m_layout->lines() - m_visible_lines;
break;
}
}
if (ev)
handle_key(ev->iptkey);
}
};
} // namespace ui

View File

@ -12,7 +12,8 @@
#pragma once
#include "ui/menu.h"
#include "ui/text.h"
#include "ui/textbox.h"
#include <optional>
#include <string>
@ -21,7 +22,7 @@
namespace ui {
class menu_about : public menu
class menu_about : public menu_textbox
{
public:
menu_about(mame_ui_manager &mui, render_container &container);
@ -30,13 +31,13 @@ public:
protected:
virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override;
virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override;
private:
virtual void draw(uint32_t flags) override;
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
std::vector<std::string> const m_header;
std::optional<text_layout> m_layout;
};
} // namespace ui

View File

@ -52,6 +52,7 @@ menu_analog::menu_analog(mame_ui_manager &mui, render_container &container)
, m_field_data()
, m_visible_fields(0U)
{
set_process_flags(PROCESS_LR_REPEAT);
}
@ -121,18 +122,15 @@ void menu_analog::custom_render(void *selectedref, float top, float bottom, floa
}
void menu_analog::handle()
void menu_analog::handle(event const *ev)
{
// process the menu
event const *const menu_event(process(PROCESS_LR_REPEAT));
// handle events
if (menu_event && menu_event->itemref)
if (ev && ev->itemref)
{
item_data &data(*reinterpret_cast<item_data *>(menu_event->itemref));
item_data &data(*reinterpret_cast<item_data *>(ev->itemref));
int newval(data.cur);
switch (menu_event->iptkey)
switch (ev->iptkey)
{
// if selected, reset to default value
case IPT_UI_SELECT:

View File

@ -66,7 +66,7 @@ private:
using field_data_vector = std::vector<field_data>;
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
void find_fields();

View File

@ -139,32 +139,28 @@ void menu_audit::populate(float &customtop, float &custombottom)
custombottom = (ui().get_line_height() * 1.0f) + (ui().box_tb_border() * 3.0f);
}
void menu_audit::handle()
void menu_audit::handle(event const *ev)
{
switch (m_phase)
{
case phase::CONFIRMATION:
if (ev && (IPT_UI_SELECT == ev->iptkey))
{
event const *const menu_event(process(0));
if (menu_event && (IPT_UI_SELECT == menu_event->iptkey))
if ((ITEMREF_START_FULL == ev->itemref) || (ITEMREF_START_FAST == ev->itemref))
{
if ((ITEMREF_START_FULL == menu_event->itemref) || (ITEMREF_START_FAST == menu_event->itemref))
{
m_phase = phase::AUDIT;
m_fast = ITEMREF_START_FAST == menu_event->itemref;
m_prompt = util::string_format(_("Press %1$s to cancel\n"), ui().get_general_input_setting(IPT_UI_CANCEL));
m_future.resize(std::thread::hardware_concurrency());
for (auto &future : m_future)
future = std::async(std::launch::async, [this] () { return do_audit(); });
}
set_process_flags(PROCESS_CUSTOM_ONLY | PROCESS_NOINPUT);
m_phase = phase::AUDIT;
m_fast = ITEMREF_START_FAST == ev->itemref;
m_prompt = util::string_format(_("Press %1$s to cancel\n"), ui().get_general_input_setting(IPT_UI_CANCEL));
m_future.resize(std::thread::hardware_concurrency());
for (auto &future : m_future)
future = std::async(std::launch::async, [this] () { return do_audit(); });
}
}
break;
case phase::AUDIT:
case phase::CANCELLATION:
process(PROCESS_CUSTOM_ONLY | PROCESS_NOINPUT);
if ((m_next.load() >= m_availablesorted.size()) || m_cancel.load())
{
bool done(true);

View File

@ -36,7 +36,7 @@ private:
enum class phase { CONFIRMATION, AUDIT, CANCELLATION };
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
bool do_audit();
void save_available_machines();

View File

@ -9,12 +9,14 @@
***************************************************************************/
#include "emu.h"
#include "ui/barcode.h"
#include "ui/ui.h"
#include "ui/utils.h"
namespace ui {
// itemrefs for key menu items
#define ITEMREF_NEW_BARCODE ((void *) 0x0001)
#define ITEMREF_ENTER_BARCODE ((void *) 0x0002)
@ -35,6 +37,7 @@ namespace ui {
menu_barcode_reader::menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device)
: menu_device_control<barcode_reader_device>(mui, container, device)
{
set_process_flags(PROCESS_LR_REPEAT);
}
@ -86,29 +89,26 @@ void menu_barcode_reader::populate(float &customtop, float &custombottom)
// handle - manages inputs in the barcode input menu
//-------------------------------------------------
void menu_barcode_reader::handle()
void menu_barcode_reader::handle(event const *ev)
{
// process the menu
const event *event = process(PROCESS_LR_REPEAT);
// process the event
if (event)
if (ev)
{
// handle selections
switch (event->iptkey)
switch (ev->iptkey)
{
case IPT_UI_LEFT:
if (event->itemref == ITEMREF_SELECT_READER)
if (ev->itemref == ITEMREF_SELECT_READER)
previous();
break;
case IPT_UI_RIGHT:
if (event->itemref == ITEMREF_SELECT_READER)
if (ev->itemref == ITEMREF_SELECT_READER)
next();
break;
case IPT_UI_SELECT:
if (event->itemref == ITEMREF_ENTER_BARCODE)
if (ev->itemref == ITEMREF_ENTER_BARCODE)
{
std::string tmp_file(m_barcode_buffer);
//printf("code %s\n", m_barcode_buffer);
@ -135,7 +135,7 @@ void menu_barcode_reader::handle()
case IPT_SPECIAL:
if (get_selection_ref() == ITEMREF_NEW_BARCODE)
{
if (input_character(m_barcode_buffer, event->unichar, uchar_is_digit))
if (input_character(m_barcode_buffer, ev->unichar, uchar_is_digit))
reset(reset_options::REMEMBER_POSITION);
}
break;

View File

@ -17,6 +17,7 @@
#include "ui/devctrl.h"
namespace ui {
class menu_barcode_reader : public menu_device_control<barcode_reader_device> {
public:
menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device);
@ -24,7 +25,7 @@ public:
private:
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
std::string m_barcode_buffer;
};

View File

@ -9,12 +9,12 @@
*********************************************************************/
#include "emu.h"
#include "cheat.h"
#include "mame.h"
#include "ui/cheatopt.h"
#include "ui/ui.h"
#include "ui/menu.h"
#include "ui/cheatopt.h"
#include "cheat.h"
#include "mame.h"
namespace ui {
@ -29,78 +29,73 @@ namespace ui {
menu_cheat - handle the cheat menu
-------------------------------------------------*/
void menu_cheat::handle()
void menu_cheat::handle(event const *ev)
{
/* process the menu */
const event *menu_event = process(PROCESS_LR_REPEAT);
/* handle events */
if (menu_event != nullptr && menu_event->itemref != nullptr)
// handle events
if (ev && ev->itemref)
{
bool changed = false;
/* clear cheat comment on any movement or keypress */
// clear cheat comment on any movement or keypress
machine().popmessage();
/* handle reset all + reset all cheats for reload all option */
if ((menu_event->itemref == ITEMREF_CHEATS_RESET_ALL || menu_event->itemref == ITEMREF_CHEATS_RELOAD_ALL) && menu_event->iptkey == IPT_UI_SELECT)
if ((ev->itemref == ITEMREF_CHEATS_RESET_ALL || ev->itemref == ITEMREF_CHEATS_RELOAD_ALL) && ev->iptkey == IPT_UI_SELECT)
{
// handle reset all + reset all cheats for reload all option
for (auto &curcheat : mame_machine_manager::instance()->cheat().entries())
if (curcheat->select_default_state())
changed = true;
}
/* handle individual cheats */
else if (menu_event->itemref >= ITEMREF_CHEATS_FIRST_ITEM)
else if (ev->itemref >= ITEMREF_CHEATS_FIRST_ITEM)
{
cheat_entry *curcheat = reinterpret_cast<cheat_entry *>(menu_event->itemref);
// handle individual cheats
cheat_entry *curcheat = reinterpret_cast<cheat_entry *>(ev->itemref);
const char *string;
switch (menu_event->iptkey)
switch (ev->iptkey)
{
/* if selected, activate a oneshot */
// if selected, activate a oneshot
case IPT_UI_SELECT:
changed = curcheat->activate();
break;
/* if cleared, reset to default value */
// if cleared, reset to default value
case IPT_UI_CLEAR:
changed = curcheat->select_default_state();
break;
/* left decrements */
// left decrements
case IPT_UI_LEFT:
changed = curcheat->select_previous_state();
break;
/* right increments */
// right increments
case IPT_UI_RIGHT:
changed = curcheat->select_next_state();
break;
/* bring up display comment if one exists */
// bring up display comment if one exists
case IPT_UI_DISPLAY_COMMENT:
case IPT_UI_UP:
case IPT_UI_DOWN:
string = curcheat->comment();
if (string != nullptr && string[0] != 0)
if (string && *string)
machine().popmessage(_("Cheat Comment:\n%s"), string);
break;
}
}
/* handle reload all */
if (menu_event->itemref == ITEMREF_CHEATS_RELOAD_ALL && menu_event->iptkey == IPT_UI_SELECT)
// handle reload all
if (ev->itemref == ITEMREF_CHEATS_RELOAD_ALL && ev->iptkey == IPT_UI_SELECT)
{
/* re-init cheat engine and thus reload cheats/cheats have already been turned off by here */
// re-init cheat engine and thus reload cheats/cheats have already been turned off by here
mame_machine_manager::instance()->cheat().reload();
/* display the reloaded cheats */
// display the reloaded cheats
reset(reset_options::REMEMBER_REF);
machine().popmessage(_("All cheats reloaded"));
}
/* if things changed, update */
// if things changed, update
if (changed)
reset(reset_options::REMEMBER_REF);
}
@ -113,6 +108,7 @@ void menu_cheat::handle()
menu_cheat::menu_cheat(mame_ui_manager &mui, render_container &container) : menu(mui, container)
{
set_process_flags(PROCESS_LR_REPEAT);
}
void menu_cheat::populate(float &customtop, float &custombottom)

View File

@ -25,7 +25,7 @@ public:
private:
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
};
} // namespace ui

View File

@ -82,6 +82,13 @@ menu_confswitch::~menu_confswitch()
}
void menu_confswitch::menu_activated()
{
// switches can have input assignments, and scripts are a thing
reset(reset_options::REMEMBER_REF);
}
void menu_confswitch::populate(float &customtop, float &custombottom)
{
// locate relevant fields if necessary
@ -162,31 +169,28 @@ void menu_confswitch::populate(float &customtop, float &custombottom)
}
item_append(menu_item_type::SEPARATOR);
item_append(_("Reset"), 0, (void *)1);
item_append(_("Reset Machine"), 0, (void *)1);
}
void menu_confswitch::handle()
void menu_confswitch::handle(event const *ev)
{
// process the menu
event const *const menu_event(process(0));
// handle events
if (menu_event && menu_event->itemref)
if (ev && ev->itemref)
{
if (uintptr_t(menu_event->itemref) == 1U)
if (uintptr_t(ev->itemref) == 1U)
{
// reset
if (menu_event->iptkey == IPT_UI_SELECT)
if (ev->iptkey == IPT_UI_SELECT)
machine().schedule_hard_reset();
}
else
{
// actual settings
ioport_field &field(*reinterpret_cast<ioport_field *>(menu_event->itemref));
ioport_field &field(*reinterpret_cast<ioport_field *>(ev->itemref));
bool changed(false);
switch (menu_event->iptkey)
switch (ev->iptkey)
{
// if selected, reset to default value
case IPT_UI_SELECT:

View File

@ -56,6 +56,7 @@ protected:
menu_confswitch(mame_ui_manager &mui, render_container &container, uint32_t type);
virtual void menu_activated() override;
virtual void populate(float &customtop, float &custombottom) override;
field_vector const &fields() { return m_fields; }
@ -63,7 +64,7 @@ protected:
unsigned active_switch_groups() const { return m_active_switch_groups; }
private:
virtual void handle() override;
virtual void handle(event const *ev) override;
void find_fields();

View File

@ -45,6 +45,7 @@ menu_custom_ui::menu_custom_ui(mame_ui_manager &mui, render_container &container
, m_currlang(0)
, m_currsysnames(0)
{
set_process_flags(PROCESS_LR_REPEAT);
find_languages();
find_sysnames();
}
@ -72,19 +73,17 @@ menu_custom_ui::~menu_custom_ui()
// handle
//-------------------------------------------------
void menu_custom_ui::handle()
void menu_custom_ui::handle(event const *ev)
{
bool changed = false;
// process the menu
const event *menu_event = process(PROCESS_LR_REPEAT);
if (menu_event != nullptr && menu_event->itemref != nullptr)
if (ev && ev->itemref)
{
switch ((uintptr_t)menu_event->itemref)
switch ((uintptr_t)ev->itemref)
{
case FONT_MENU:
if (menu_event->iptkey == IPT_UI_SELECT)
if (ev->iptkey == IPT_UI_SELECT)
menu::stack_push<menu_font_ui>(
ui(),
container(),
@ -95,16 +94,16 @@ void menu_custom_ui::handle()
});
break;
case COLORS_MENU:
if (menu_event->iptkey == IPT_UI_SELECT)
if (ev->iptkey == IPT_UI_SELECT)
menu::stack_push<menu_colors_ui>(ui(), container());
break;
case HIDE_MENU:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT)
if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{
changed = true;
(menu_event->iptkey == IPT_UI_RIGHT) ? ui_globals::panels_status++ : ui_globals::panels_status--;
(ev->iptkey == IPT_UI_RIGHT) ? ui_globals::panels_status++ : ui_globals::panels_status--;
}
else if (menu_event->iptkey == IPT_UI_SELECT)
else if (ev->iptkey == IPT_UI_SELECT)
{
std::vector<std::string> s_sel(std::size(HIDE_STATUS));
std::transform(std::begin(HIDE_STATUS), std::end(HIDE_STATUS), s_sel.begin(), [](auto &s) { return _(s); });
@ -118,15 +117,15 @@ void menu_custom_ui::handle()
}
break;
case LANGUAGE_MENU:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT)
if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{
changed = true;
if (menu_event->iptkey == IPT_UI_LEFT)
if (ev->iptkey == IPT_UI_LEFT)
m_currlang = (m_currlang ? m_currlang : m_languages.size())- 1;
else if (++m_currlang >= m_languages.size())
m_currlang = 0;
}
else if (menu_event->iptkey == IPT_UI_SELECT)
else if (ev->iptkey == IPT_UI_SELECT)
{
// copying list of language names - expensive
menu::stack_push<menu_selector>(
@ -139,15 +138,15 @@ void menu_custom_ui::handle()
}
break;
case SYSNAMES_MENU:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT)
if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{
changed = true;
if (menu_event->iptkey == IPT_UI_LEFT)
if (ev->iptkey == IPT_UI_LEFT)
m_currsysnames = (m_currsysnames ? m_currsysnames : m_sysnames.size())- 1;
else if (++m_currsysnames >= m_sysnames.size())
m_currsysnames = 0;
}
else if (menu_event->iptkey == IPT_UI_SELECT)
else if (ev->iptkey == IPT_UI_SELECT)
{
// copying list of file names - expensive
menu::stack_push<menu_selector>(
@ -308,6 +307,8 @@ menu_font_ui::menu_font_ui(mame_ui_manager &mui, render_container &container, st
, m_changed(false)
, m_actual(0U)
{
set_process_flags(PROCESS_LR_REPEAT);
std::string name(mui.machine().options().ui_font());
list();
@ -374,41 +375,39 @@ menu_font_ui::~menu_font_ui()
// handle
//-------------------------------------------------
void menu_font_ui::handle()
void menu_font_ui::handle(event const *ev)
{
bool changed = false;
// process the menu
const event *menu_event = process(PROCESS_LR_REPEAT);
if (menu_event && menu_event->itemref)
if (ev && ev->itemref)
{
switch ((uintptr_t)menu_event->itemref)
switch ((uintptr_t)ev->itemref)
{
case INFOS_SIZE:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT)
if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{
(menu_event->iptkey == IPT_UI_RIGHT) ? m_info_size += 0.05f : m_info_size -= 0.05f;
(ev->iptkey == IPT_UI_RIGHT) ? m_info_size += 0.05f : m_info_size -= 0.05f;
changed = true;
}
break;
case FONT_SIZE:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT)
if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{
(menu_event->iptkey == IPT_UI_RIGHT) ? m_font_size++ : m_font_size--;
(ev->iptkey == IPT_UI_RIGHT) ? m_font_size++ : m_font_size--;
changed = true;
}
break;
case MUI_FNT:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT)
if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT)
{
(menu_event->iptkey == IPT_UI_RIGHT) ? m_actual++ : m_actual--;
(ev->iptkey == IPT_UI_RIGHT) ? m_actual++ : m_actual--;
changed = true;
}
else if (menu_event->iptkey == IPT_UI_SELECT)
else if (ev->iptkey == IPT_UI_SELECT)
{
std::vector<std::string> display_names;
display_names.reserve(m_fonts.size());
@ -428,9 +427,9 @@ void menu_font_ui::handle()
#ifdef UI_WINDOWS
case MUI_BOLD:
case MUI_ITALIC:
if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT)
if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT || ev->iptkey == IPT_UI_SELECT)
{
((uintptr_t)menu_event->itemref == MUI_BOLD) ? m_bold = !m_bold : m_italic = !m_italic;
((uintptr_t)ev->itemref == MUI_BOLD) ? m_bold = !m_bold : m_italic = !m_italic;
changed = true;
}
break;
@ -551,17 +550,17 @@ menu_colors_ui::~menu_colors_ui()
// handle
//-------------------------------------------------
void menu_colors_ui::handle()
void menu_colors_ui::handle(event const *ev)
{
bool changed = false;
// process the menu
const event *menu_event = process(0);
if (menu_event != nullptr && menu_event->itemref != nullptr && menu_event->iptkey == IPT_UI_SELECT)
if (ev && ev->itemref && ev->iptkey == IPT_UI_SELECT)
{
if ((uintptr_t)menu_event->itemref != MUI_RESTORE)
menu::stack_push<menu_rgb_ui>(ui(), container(), &m_color_table[(uintptr_t)menu_event->itemref].color, selected_item().text);
if ((uintptr_t)ev->itemref != MUI_RESTORE)
{
menu::stack_push<menu_rgb_ui>(ui(), container(), &m_color_table[(uintptr_t)ev->itemref].color, selected_item().text);
}
else
{
changed = true;
@ -767,6 +766,7 @@ menu_rgb_ui::menu_rgb_ui(mame_ui_manager &mui, render_container &container, rgb_
m_lock_ref(0),
m_title(_title)
{
set_process_flags(PROCESS_LR_REPEAT);
}
//-------------------------------------------------
@ -781,26 +781,19 @@ menu_rgb_ui::~menu_rgb_ui()
// handle
//-------------------------------------------------
void menu_rgb_ui::handle()
void menu_rgb_ui::handle(event const *ev)
{
// process the menu
const event *menu_event;
if (!m_key_active)
menu_event = process(PROCESS_LR_REPEAT);
else
menu_event = process(PROCESS_ONLYCHAR);
if (menu_event && menu_event->itemref != nullptr)
if (ev && ev->itemref)
{
bool changed = false;
switch (menu_event->iptkey)
switch (ev->iptkey)
{
case IPT_UI_LEFT:
case IPT_UI_RIGHT:
{
int updated = (IPT_UI_LEFT == menu_event->iptkey) ? -1 : 1;
switch (uintptr_t(menu_event->itemref))
int updated = (IPT_UI_LEFT == ev->iptkey) ? -1 : 1;
switch (uintptr_t(ev->itemref))
{
case RGB_ALPHA:
updated += m_color->a();
@ -839,20 +832,20 @@ void menu_rgb_ui::handle()
break;
case IPT_UI_SELECT:
if (uintptr_t(menu_event->itemref) == PALETTE_CHOOSE)
if (uintptr_t(ev->itemref) == PALETTE_CHOOSE)
{
menu::stack_push<menu_palette_sel>(ui(), container(), *m_color);
break;
}
[[fallthrough]];
case IPT_SPECIAL:
switch (uintptr_t(menu_event->itemref))
switch (uintptr_t(ev->itemref))
{
case RGB_ALPHA:
case RGB_RED:
case RGB_GREEN:
case RGB_BLUE:
inkey_special(menu_event);
inkey_special(ev);
changed = true;
break;
}
@ -1004,6 +997,7 @@ void menu_rgb_ui::inkey_special(const event *menu_event)
if (menu_event->iptkey == IPT_UI_SELECT)
{
m_key_active = !m_key_active;
set_process_flags(m_key_active ? PROCESS_ONLYCHAR : PROCESS_LR_REPEAT);
m_lock_ref = (uintptr_t)menu_event->itemref;
if (!m_key_active)
@ -1079,13 +1073,12 @@ menu_palette_sel::~menu_palette_sel()
// handle
//-------------------------------------------------
void menu_palette_sel::handle()
void menu_palette_sel::handle(event const *ev)
{
// process the menu
const event *menu_event = process(0);
if (menu_event != nullptr && menu_event->itemref != nullptr)
if (ev && ev->itemref)
{
if (menu_event->iptkey == IPT_UI_SELECT)
if (ev->iptkey == IPT_UI_SELECT)
{
m_original = rgb_t(uint32_t(strtoul(selected_item().subtext.c_str(), nullptr, 16)));
reset_parent(reset_options::SELECT_FIRST);

View File

@ -44,7 +44,7 @@ private:
};
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
void find_languages();
void find_sysnames();
@ -82,7 +82,7 @@ private:
};
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
void list();
@ -143,7 +143,7 @@ private:
};
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
s_color_table m_color_table[MUI_RESTORE];
void restore_colors();
@ -173,7 +173,7 @@ private:
};
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
void inkey_special(const event *menu_event);
@ -196,7 +196,7 @@ public:
private:
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
static std::pair<const char *, const char *> const s_palette[];
rgb_t &m_original;

View File

@ -24,6 +24,7 @@
#include "uiinput.h"
#include <cmath>
#include <limits>
#include <string_view>
@ -34,14 +35,14 @@ namespace ui {
//-------------------------------------------------
menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system)
: menu(mui, container)
: menu_textbox(mui, container)
, m_system(!system ? &system_list::instance().systems()[driver_list::find(mui.machine().system().name)] : system)
, m_swinfo(nullptr)
, m_issoft(false)
, m_layout()
, m_actual(0)
{
set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV);
for (device_image_interface& image : image_interface_enumerator(mui.machine().root_device()))
{
if (image.filename())
@ -71,11 +72,10 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container
//-------------------------------------------------
menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo)
: menu(mui, container)
: menu_textbox(mui, container)
, m_system(nullptr)
, m_swinfo(&swinfo)
, m_issoft(true)
, m_layout()
, m_actual(0)
, m_list(swinfo.listname)
, m_short(swinfo.shortname)
@ -83,6 +83,7 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container
, m_parent(swinfo.parentname)
{
set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV);
if (!swinfo.infotext.empty())
m_items_list.emplace_back(_("Software List Info"), 0, "");
std::vector<std::string> lua_list;
@ -165,18 +166,17 @@ void menu_dats_view::add_info_text(text_layout &layout, std::string_view text, r
// handle
//-------------------------------------------------
void menu_dats_view::handle()
void menu_dats_view::handle(event const *ev)
{
event const *const menu_event = process(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV);
if (menu_event)
if (ev)
{
switch (menu_event->iptkey)
switch (ev->iptkey)
{
case IPT_UI_LEFT:
if (m_actual > 0)
{
m_actual--;
reset(reset_options::SELECT_FIRST);
reset_layout();
}
break;
@ -184,33 +184,12 @@ void menu_dats_view::handle()
if ((m_actual + 1) < m_items_list.size())
{
m_actual++;
reset(reset_options::SELECT_FIRST);
reset_layout();
}
break;
case IPT_UI_UP:
--top_line;
break;
case IPT_UI_DOWN:
++top_line;
break;
case IPT_UI_PAGE_UP:
top_line -= m_visible_lines - 3;
break;
case IPT_UI_PAGE_DOWN:
top_line += m_visible_lines - 3;
break;
case IPT_UI_HOME:
top_line = 0;
break;
case IPT_UI_END:
top_line = m_layout->lines() - m_visible_lines;
break;
default:
handle_key(ev->iptkey);
}
}
}
@ -221,150 +200,8 @@ void menu_dats_view::handle()
void menu_dats_view::populate(float &customtop, float &custombottom)
{
bool paused = machine().paused();
if (!paused)
machine().pause();
m_layout = std::nullopt;
customtop = 2.0f * ui().get_line_height() + 4.0f * ui().box_tb_border();
custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border();
if (!paused)
machine().resume();
}
//-------------------------------------------------
// draw - draw dats menu
//-------------------------------------------------
void menu_dats_view::draw(uint32_t flags)
{
float const aspect = machine().render().ui_aspect(&container());
float const line_height = ui().get_line_height();
float const ud_arrow_width = line_height * aspect;
float const gutter_width = 0.52f * line_height * aspect;
float const visible_width = 1.0f - (2.0f * ui().box_lr_border() * aspect);
float const visible_left = (1.0f - visible_width) * 0.5f;
float const extra_height = 2.0f * line_height;
float const visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height;
// determine effective positions taking into account the hilighting arrows
float const effective_width = visible_width - 2.0f * gutter_width;
float const effective_left = visible_left + gutter_width;
draw_background();
map_mouse();
// account for extra space at the top and bottom
float visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height;
m_visible_lines = int(std::trunc(visible_main_menu_height / line_height));
visible_main_menu_height = float(m_visible_lines) * line_height;
// compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust
float const visible_top = ((1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f) + get_customtop();
// compute text box size
float const x1 = visible_left;
float const y1 = visible_top - ui().box_tb_border();
float const x2 = x1 + visible_width;
float const y2 = visible_top + visible_main_menu_height + ui().box_tb_border() + extra_height;
float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH;
float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH;
float const separator = visible_top + float(m_visible_lines) * line_height;
ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color());
if (!m_layout || (m_layout->width() != effective_width))
{
std::string buffer;
if (!m_items_list.empty())
{
if (m_issoft)
get_data_sw(buffer);
else
get_data(buffer);
}
m_layout.emplace(ui().create_layout(container(), effective_width));
add_info_text(*m_layout, buffer, ui().colors().text_color());
}
int const visible_items = m_layout->lines();
m_visible_lines = (std::min)(visible_items, m_visible_lines);
top_line = (std::max)(0, top_line);
if (top_line + m_visible_lines >= visible_items)
top_line = visible_items - m_visible_lines;
clear_hover();
if (top_line)
{
// if we're on the top line, display the up arrow
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, visible_top, line_x1, visible_top + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, visible_top,
line_x1, visible_top + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_UP);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, visible_top + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, visible_top + 0.75f * line_height,
fgcolor, ROT0);
}
if ((top_line + m_visible_lines) < visible_items)
{
// if we're on the bottom line, display the down arrow
float const line_y = visible_top + float(m_visible_lines - 1) * line_height;
rgb_t fgcolor = ui().colors().text_color();
if (mouse_in_rect(line_x0, line_y, line_x1, line_y + line_height))
{
fgcolor = ui().colors().mouseover_color();
highlight(
line_x0, line_y,
line_x1, line_y + line_height,
ui().colors().mouseover_bg_color());
set_hover(HOVER_ARROW_DOWN);
}
draw_arrow(
0.5f * (x1 + x2) - 0.5f * ud_arrow_width, line_y + 0.25f * line_height,
0.5f * (x1 + x2) + 0.5f * ud_arrow_width, line_y + 0.75f * line_height,
fgcolor, ROT0 ^ ORIENTATION_FLIP_Y);
}
// return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow
m_visible_items = m_visible_lines - (top_line ? 1 : 0) - (top_line + m_visible_lines != visible_items);
m_layout->emit(
container(),
top_line ? (top_line + 1) : 0, m_visible_items,
effective_left, visible_top + (top_line ? line_height : 0.0f));
// add visual separator before the "return to prevous menu" item
container().add_line(
x1, separator + (0.5f * line_height),
x2, separator + (0.5f * line_height),
UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
menu_item const &pitem = item(0);
std::string_view const itemtext = pitem.text;
float const line_y0 = separator + line_height;
float const line_y1 = line_y0 + line_height;
if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem))
set_hover(0);
highlight(line_x0, line_y0, line_x1, line_y1, ui().colors().selected_bg_color());
ui().draw_text_full(
container(), itemtext,
effective_left, line_y0, effective_width,
text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE,
mame_ui_manager::NORMAL,
ui().colors().selected_color(), ui().colors().selected_bg_color(),
nullptr, nullptr);
// if there is something special to add, do it by calling the virtual method
custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2);
}
//-------------------------------------------------
@ -512,7 +349,7 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f
}
//-------------------------------------------------
// load data from DATs
// custom mouse click handling
//-------------------------------------------------
bool menu_dats_view::custom_mouse_down()
@ -532,6 +369,28 @@ bool menu_dats_view::custom_mouse_down()
}
}
//-------------------------------------------------
// populate selected DAT text
//-------------------------------------------------
void menu_dats_view::populate_text(std::optional<text_layout> &layout, float &width, int &lines)
{
if (!layout || (layout->width() != width))
{
std::string buffer;
if (!m_items_list.empty())
{
if (m_issoft)
get_data_sw(buffer);
else
get_data(buffer);
}
layout.emplace(ui().create_layout(container(), width));
add_info_text(*layout, buffer, ui().colors().text_color());
lines = std::numeric_limits<int>::max();
}
}
//-------------------------------------------------
// load data from DATs
//-------------------------------------------------

View File

@ -14,8 +14,8 @@
#pragma once
#include "ui/menu.h"
#include "ui/text.h"
#include "ui/textbox.h"
#include <optional>
#include <string>
@ -33,7 +33,7 @@ namespace ui {
// class dats menu
//-------------------------------------------------
class menu_dats_view : public menu
class menu_dats_view : public menu_textbox
{
public:
menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo);
@ -46,6 +46,8 @@ protected:
virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override;
virtual bool custom_mouse_down() override;
virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override;
private:
struct list_items
{
@ -56,11 +58,8 @@ private:
std::string revision;
};
// draw dats menu
virtual void draw(uint32_t flags) override;
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
virtual void handle(event const *ev) override;
void get_data(std::string &buffer);
void get_data_sw(std::string &buffer);
@ -68,7 +67,6 @@ private:
ui_system_info const *const m_system;
ui_software_info const *const m_swinfo;
bool const m_issoft;
std::optional<text_layout> m_layout;
int m_actual;
std::string m_list, m_short, m_long, m_parent;
std::vector<list_items> m_items_list;

Some files were not shown because too many files have changed in this diff Show More