UI input mapping menu updates:

* When a switch-type input is selected, show feedback when it's pressed
* If an invalid code is entered (e.g. only negatives) abandon the change rather than cycling default/none
* If an invalid code is entered display a message until the user takes some other action

input.cpp updates:
* constexpr crusade on input_code and input_seq and some very slight optimisation to input_seq
* seq_poll* is a frontend function and had no business being in the core, so it's a utility class now
* seq_poll* now exposes a bit more detail, enabling improved interaction on the UI inputs menu
* global state is reduced a little, but the poll_* functions are still members of the input manager with global state

(nw) The Lua engine has been updated in a way that maintains source compatibility with existing Lua
scripts.  This is less than ideal, but it minimises impact.  Ideally someone (possibly me) will be
able to expose the input sequence poller helper properly.  I tested the changes with the cheat and
autofire plugins and I was able to assign sequences.  However I found two issues: it's seems
impossible to assign a more complex sequence than a single key/button in the autofire plugin (i.e.
no AND or NOT conditions, I confirmed this is pre-existing, not a regression), and in both the cheat
and autofire plugins I found it a bit unwieldy trying to enter a complex sequence without live
feedback of the sequence as it's built (this was also applicable to MAME's own input mapping menu
until I added the live display yesterday).
This commit is contained in:
Vas Crabb 2019-11-21 03:36:19 +11:00
parent f5d23f7036
commit 6c61aa61d2
16 changed files with 461 additions and 300 deletions

View File

@ -63,8 +63,10 @@ files {
MAME_DIR .. "src/frontend/mame/cheat.h",
MAME_DIR .. "src/frontend/mame/clifront.cpp",
MAME_DIR .. "src/frontend/mame/clifront.h",
MAME_DIR .. "src/frontend/mame/info.cpp",
MAME_DIR .. "src/frontend/mame/info.h",
MAME_DIR .. "src/frontend/mame/infoxml.cpp",
MAME_DIR .. "src/frontend/mame/infoxml.h",
MAME_DIR .. "src/frontend/mame/iptseqpoll.cpp",
MAME_DIR .. "src/frontend/mame/iptseqpoll.h",
MAME_DIR .. "src/frontend/mame/language.cpp",
MAME_DIR .. "src/frontend/mame/language.h",
MAME_DIR .. "src/frontend/mame/luaengine.cpp",

View File

@ -30,10 +30,10 @@
const s32 INVALID_AXIS_VALUE = 0x7fffffff;
// additional expanded input codes for sequences
const input_code input_seq::end_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_END);
const input_code input_seq::default_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_DEFAULT);
const input_code input_seq::not_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_NOT);
const input_code input_seq::or_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_OR);
constexpr input_code input_seq::end_code;
constexpr input_code input_seq::default_code;
constexpr input_code input_seq::not_code;
constexpr input_code input_seq::or_code;
// constant sequences
const input_seq input_seq::empty_seq;
@ -377,14 +377,15 @@ static const code_string_table itemid_token_table[] =
// input sequence
//-------------------------------------------------
input_seq &input_seq::operator+=(input_code code)
input_seq &input_seq::operator+=(input_code code) noexcept
{
// if not enough room, return false
int curlength = length();
if (curlength < ARRAY_LENGTH(m_code) - 1)
const int curlength = length();
if (curlength < m_code.size())
{
m_code[curlength++] = code;
m_code[curlength] = end_code;
m_code[curlength] = code;
if ((curlength + 1) < m_code.size())
m_code[curlength + 1] = end_code;
}
return *this;
}
@ -396,17 +397,25 @@ input_seq &input_seq::operator+=(input_code code)
// before the new code
//-------------------------------------------------
input_seq &input_seq::operator|=(input_code code)
input_seq &input_seq::operator|=(input_code code) noexcept
{
// overwrite end/default with the new code
if (m_code[0] == end_code || m_code[0] == default_code)
if (m_code[0] == default_code)
{
m_code[0] = code;
// otherwise, append an OR token and then the new code
m_code[1] = end_code;
}
else
{
*this += or_code;
*this += code;
// otherwise, append an OR token and then the new code
const int curlength = length();
if ((curlength + 1) < m_code.size())
{
m_code[curlength] = or_code;
m_code[curlength + 1] = code;
if ((curlength + 2) < m_code.size())
m_code[curlength + 2] = end_code;
}
}
return *this;
}
@ -416,13 +425,13 @@ input_seq &input_seq::operator|=(input_code code)
// length - return the length of the sequence
//-------------------------------------------------
int input_seq::length() const
int input_seq::length() const noexcept
{
// find the end token; error if none found
for (int seqnum = 0; seqnum < ARRAY_LENGTH(m_code); seqnum++)
for (int seqnum = 0; seqnum < m_code.size(); seqnum++)
if (m_code[seqnum] == end_code)
return seqnum;
return ARRAY_LENGTH(m_code);
return m_code.size();
}
@ -431,17 +440,17 @@ int input_seq::length() const
// valid
//-------------------------------------------------
bool input_seq::is_valid() const
bool input_seq::is_valid() const noexcept
{
// "default" can only be of length 1
if (m_code[0] == default_code)
return (length() == 1);
return m_code[1] == end_code;
// scan the sequence for valid codes
input_item_class lastclass = ITEM_CLASS_INVALID;
input_code lastcode = INPUT_CODE_INVALID;
int positive_code_count = 0;
for (auto code : m_code)
for (input_code code : m_code)
{
// invalid codes are never permitted
if (code == INPUT_CODE_INVALID)
@ -451,7 +460,7 @@ bool input_seq::is_valid() const
if (code == or_code || code == end_code)
{
// must be at least one positive code
if (positive_code_count == 0)
if (!positive_code_count)
return false;
// last code must not have been an internal code
@ -466,15 +475,12 @@ bool input_seq::is_valid() const
positive_code_count = 0;
lastclass = ITEM_CLASS_INVALID;
}
// if we hit a NOT, make sure we don't have a double
else if (code == not_code)
{
// if we hit a NOT, make sure we don't have a double
if (lastcode == not_code)
return false;
}
// anything else
else
{
// count positive codes
@ -501,33 +507,15 @@ bool input_seq::is_valid() const
}
//-------------------------------------------------
// set - directly set up to the first 7 codes
//-------------------------------------------------
void input_seq::set(input_code code0, input_code code1, input_code code2, input_code code3, input_code code4, input_code code5, input_code code6)
{
m_code[0] = code0;
m_code[1] = code1;
m_code[2] = code2;
m_code[3] = code3;
m_code[4] = code4;
m_code[5] = code5;
m_code[6] = code6;
for (int codenum = 7; codenum < ARRAY_LENGTH(m_code); codenum++)
m_code[codenum] = end_code;
}
//-------------------------------------------------
// backspace - "backspace" over the last entry in
// a sequence
//-------------------------------------------------
void input_seq::backspace()
void input_seq::backspace() noexcept
{
// if we have at least one entry, remove it
int curlength = length();
const int curlength = length();
if (curlength > 0)
m_code[curlength - 1] = end_code;
}
@ -538,9 +526,9 @@ void input_seq::backspace()
// with newcode in a sequence
//-------------------------------------------------
void input_seq::replace(input_code oldcode, input_code newcode)
void input_seq::replace(input_code oldcode, input_code newcode) noexcept
{
for (auto & elem : m_code)
for (input_code &elem : m_code)
if (elem == oldcode)
elem = newcode;
}
@ -555,10 +543,7 @@ void input_seq::replace(input_code oldcode, input_code newcode)
// input_manager - constructor
//-------------------------------------------------
input_manager::input_manager(running_machine &machine)
: m_machine(machine),
m_poll_seq_last_ticks(0),
m_poll_seq_class(ITEM_CLASS_SWITCH)
input_manager::input_manager(running_machine &machine) : m_machine(machine)
{
// reset code memory
reset_memory();
@ -1338,129 +1323,6 @@ s32 input_manager::seq_axis_value(const input_seq &seq, input_item_class &itemcl
}
//-------------------------------------------------
// seq_poll_start - begin polling for a new
// sequence of the given itemclass
//-------------------------------------------------
void input_manager::seq_poll_start(input_item_class itemclass, const input_seq *startseq)
{
assert(itemclass == ITEM_CLASS_SWITCH || itemclass == ITEM_CLASS_ABSOLUTE || itemclass == ITEM_CLASS_RELATIVE);
// reset the recording count and the clock
m_poll_seq_last_ticks = 0;
m_poll_seq_class = itemclass;
m_poll_seq.reset();
// grab the starting sequence to append to, and append an OR
if (startseq != nullptr)
{
m_poll_seq = *startseq;
if (m_poll_seq.length() > 0)
m_poll_seq += input_seq::or_code;
}
// flush out any goobers
reset_polling();
input_code dummycode = KEYCODE_ENTER;
while (dummycode != INPUT_CODE_INVALID)
dummycode = (m_poll_seq_class == ITEM_CLASS_SWITCH) ? poll_switches() : poll_axes();
}
//-------------------------------------------------
// input_seq_poll - continue polling
//-------------------------------------------------
bool input_manager::seq_poll()
{
const int curlen = m_poll_seq.length();
input_code lastcode = m_poll_seq[curlen - 1];
input_code newcode;
if (m_poll_seq_class == ITEM_CLASS_SWITCH)
{
// switch case: see if we have a new code to process
newcode = poll_switches();
if (newcode != INPUT_CODE_INVALID)
{
// if code is duplicate, toggle the NOT state on the code
if (curlen > 0 && newcode == lastcode)
{
// back up over the existing code
m_poll_seq.backspace();
// if there was a NOT preceding it, delete it as well, otherwise append a fresh one
if (m_poll_seq[curlen - 2] == input_seq::not_code)
m_poll_seq.backspace();
else
m_poll_seq += input_seq::not_code;
}
}
}
else
{
// absolute/relative case: see if we have an analog change of sufficient amount
bool has_or = false;
if (lastcode == input_seq::or_code)
{
lastcode = m_poll_seq[curlen - 2];
has_or = true;
}
newcode = poll_axes();
// if the last code doesn't match absolute/relative of this code, ignore the new one
if ((lastcode.item_class() == ITEM_CLASS_ABSOLUTE && newcode.item_class() != ITEM_CLASS_ABSOLUTE) ||
(lastcode.item_class() == ITEM_CLASS_RELATIVE && newcode.item_class() != ITEM_CLASS_RELATIVE))
newcode = INPUT_CODE_INVALID;
// if the new code is valid, check for half-axis toggles on absolute controls
if (newcode != INPUT_CODE_INVALID && curlen > 0 && newcode.item_class() == ITEM_CLASS_ABSOLUTE)
{
input_code last_nomodifier = lastcode;
last_nomodifier.set_item_modifier(ITEM_MODIFIER_NONE);
if (newcode == last_nomodifier)
{
// increment the modifier, wrapping back to none
switch (lastcode.item_modifier())
{
case ITEM_MODIFIER_NONE: newcode.set_item_modifier(ITEM_MODIFIER_POS); break;
case ITEM_MODIFIER_POS: newcode.set_item_modifier(ITEM_MODIFIER_NEG); break;
default:
case ITEM_MODIFIER_NEG: newcode.set_item_modifier(ITEM_MODIFIER_NONE); break;
}
// back up over the previous code so we can re-append
if (has_or)
m_poll_seq.backspace();
m_poll_seq.backspace();
}
}
}
// if we got a new code to append it, append it and reset the timer
if (newcode != INPUT_CODE_INVALID)
{
m_poll_seq += newcode;
m_poll_seq_last_ticks = osd_ticks();
}
// if we're recorded at least one item and 2/3 of a second has passed, we're done
if (m_poll_seq_last_ticks != 0 && osd_ticks() > m_poll_seq_last_ticks + osd_ticks_per_second() * 2 / 3)
{
// if the final result is invalid, reset to nothing
if (!m_poll_seq.is_valid())
m_poll_seq.reset();
// return true to indicate that we are finished
return true;
}
// return false to indicate we are still polling
return false;
}
//-------------------------------------------------
// seq_clean - clean the sequence, removing
// any invalid bits
@ -1505,7 +1367,7 @@ std::string input_manager::seq_name(const input_seq &seq) const
// special case: empty
if (cleaned_seq[0] == input_seq::end_code)
return std::string((seq.length() == 0) ? "None" : "n/a");
return std::string(seq.empty() ? "None" : "n/a");
// start with an empty buffer
std::string str;

View File

@ -17,6 +17,10 @@
#ifndef MAME_EMU_INPUT_H
#define MAME_EMU_INPUT_H
#include <algorithm>
#include <array>
#include <iterator>
//**************************************************************************
// CONSTANTS
@ -358,7 +362,12 @@ class input_code
{
public:
// construction/destruction
input_code(input_device_class devclass = DEVICE_CLASS_INVALID, int devindex = 0, input_item_class itemclass = ITEM_CLASS_INVALID, input_item_modifier modifier = ITEM_MODIFIER_NONE, input_item_id itemid = ITEM_ID_INVALID)
constexpr input_code(
input_device_class devclass = DEVICE_CLASS_INVALID,
int devindex = 0,
input_item_class itemclass = ITEM_CLASS_INVALID,
input_item_modifier modifier = ITEM_MODIFIER_NONE,
input_item_id itemid = ITEM_ID_INVALID) noexcept
: m_internal(((devclass & 0xf) << 28) | ((devindex & 0xff) << 20) | ((itemclass & 0xf) << 16) | ((modifier & 0xf) << 12) | (itemid & 0xfff))
{
assert(devclass >= 0 && devclass < DEVICE_CLASS_MAXIMUM);
@ -367,30 +376,48 @@ public:
assert(modifier >= 0 && modifier < ITEM_MODIFIER_MAXIMUM);
assert(itemid >= 0 && itemid < ITEM_ID_ABSOLUTE_MAXIMUM);
}
input_code(const input_code &src)
: m_internal(src.m_internal) { }
constexpr input_code(const input_code &src) noexcept = default;
// operators
bool operator==(const input_code &rhs) const { return m_internal == rhs.m_internal; }
bool operator!=(const input_code &rhs) const { return m_internal != rhs.m_internal; }
constexpr bool operator==(const input_code &rhs) const noexcept { return m_internal == rhs.m_internal; }
constexpr bool operator!=(const input_code &rhs) const noexcept { return m_internal != rhs.m_internal; }
// getters
bool internal() const { return device_class() == DEVICE_CLASS_INTERNAL; }
input_device_class device_class() const { return input_device_class((m_internal >> 28) & 0xf); }
int device_index() const { return ((m_internal >> 20) & 0xff); }
input_item_class item_class() const { return input_item_class((m_internal >> 16) & 0xf); }
input_item_modifier item_modifier() const { return input_item_modifier((m_internal >> 12) & 0xf); }
input_item_id item_id() const { return input_item_id(m_internal & 0xfff); }
constexpr bool internal() const noexcept { return device_class() == DEVICE_CLASS_INTERNAL; }
constexpr input_device_class device_class() const noexcept { return input_device_class((m_internal >> 28) & 0xf); }
constexpr int device_index() const noexcept { return ((m_internal >> 20) & 0xff); }
constexpr input_item_class item_class() const noexcept { return input_item_class((m_internal >> 16) & 0xf); }
constexpr input_item_modifier item_modifier() const noexcept { return input_item_modifier((m_internal >> 12) & 0xf); }
constexpr input_item_id item_id() const noexcept { return input_item_id(m_internal & 0xfff); }
// setters
void set_device_class(input_device_class devclass) { assert(devclass >= 0 && devclass <= 0xf); m_internal = (m_internal & ~(0xf << 28)) | ((devclass & 0xf) << 28); }
void set_device_index(int devindex) { assert(devindex >= 0 && devindex <= 0xff); m_internal = (m_internal & ~(0xff << 20)) | ((devindex & 0xff) << 20); }
void set_item_class(input_item_class itemclass) { assert(itemclass >= 0 && itemclass <= 0xf); m_internal = (m_internal & ~(0xf << 16)) | ((itemclass & 0xf) << 16); }
void set_item_modifier(input_item_modifier modifier) { assert(modifier >= 0 && modifier <= 0xf); m_internal = (m_internal & ~(0xf << 12)) | ((modifier & 0xf) << 12); }
void set_item_id(input_item_id itemid) { assert(itemid >= 0 && itemid <= 0xfff); m_internal = (m_internal & ~0xfff) | (itemid & 0xfff); }
void set_device_class(input_device_class devclass) noexcept
{
assert(devclass >= 0 && devclass <= 0xf);
m_internal = (m_internal & ~(0xf << 28)) | ((devclass & 0xf) << 28);
}
void set_device_index(int devindex) noexcept
{
assert(devindex >= 0 && devindex <= 0xff);
m_internal = (m_internal & ~(0xff << 20)) | ((devindex & 0xff) << 20);
}
void set_item_class(input_item_class itemclass) noexcept
{
assert(itemclass >= 0 && itemclass <= 0xf);
m_internal = (m_internal & ~(0xf << 16)) | ((itemclass & 0xf) << 16);
}
void set_item_modifier(input_item_modifier modifier) noexcept
{
assert(modifier >= 0 && modifier <= 0xf);
m_internal = (m_internal & ~(0xf << 12)) | ((modifier & 0xf) << 12);
}
void set_item_id(input_item_id itemid) noexcept
{
assert(itemid >= 0 && itemid <= 0xfff);
m_internal = (m_internal & ~0xfff) | (itemid & 0xfff);
}
private:
// internal state
u32 m_internal;
};
@ -402,41 +429,59 @@ class input_seq
{
public:
// construction/destruction
input_seq(input_code code0 = input_seq::end_code, input_code code1 = input_seq::end_code, input_code code2 = input_seq::end_code, input_code code3 = input_seq::end_code, input_code code4 = input_seq::end_code, input_code code5 = input_seq::end_code, input_code code6 = input_seq::end_code)
{ set(code0, code1, code2, code3, code4, code5, code6); }
input_seq(const input_seq &rhs) { memcpy(m_code, rhs.m_code, sizeof(m_code)); }
template <typename... T> constexpr input_seq(input_code code_0 = end_code, T... code_n) noexcept
{
set(code_0, code_n...);
}
constexpr input_seq(const input_seq &rhs) noexcept = default;
// operators
bool operator==(const input_seq &rhs) const { return (memcmp(m_code, rhs.m_code, sizeof(m_code)) == 0); }
bool operator!=(const input_seq &rhs) const { return (memcmp(m_code, rhs.m_code, sizeof(m_code)) != 0); }
input_code operator[](int index) const { return (index >= 0 && index < ARRAY_LENGTH(m_code)) ? m_code[index] : input_seq::end_code; }
input_seq &operator+=(input_code code);
input_seq &operator|=(input_code code);
bool operator==(const input_seq &rhs) const noexcept { return m_code == rhs.m_code; }
bool operator!=(const input_seq &rhs) const noexcept { return m_code != rhs.m_code; }
constexpr input_code operator[](int index) const noexcept { return (index >= 0 && index < m_code.size()) ? m_code[index] : end_code; }
input_seq &operator+=(input_code code) noexcept;
input_seq &operator|=(input_code code) noexcept;
// getters
int length() const;
bool is_valid() const;
bool is_default() const { return m_code[0] == default_code; }
constexpr bool empty() const noexcept { return m_code[0] == end_code; }
constexpr int max_size() const noexcept { return std::tuple_size<decltype(m_code)>::value; }
int length() const noexcept;
bool is_valid() const noexcept;
constexpr bool is_default() const noexcept { return m_code[0] == default_code; }
// setters
void set(input_code code0 = input_seq::end_code, input_code code1 = input_seq::end_code, input_code code2 = input_seq::end_code, input_code code3 = input_seq::end_code, input_code code4 = input_seq::end_code, input_code code5 = input_seq::end_code, input_code code6 = input_seq::end_code);
void reset() { set(); }
void set_default() { set(default_code); }
void backspace();
void replace(input_code oldcode, input_code newcode);
template <typename... T> void set(input_code code_0, T... code_n) noexcept
{
static_assert(sizeof...(T) < std::tuple_size<decltype(m_code)>::value, "too many codes for input_seq");
set<0>(code_0, code_n...);
}
void reset() noexcept { set(end_code); }
void set_default() noexcept { set(default_code); }
void backspace() noexcept;
void replace(input_code oldcode, input_code newcode) noexcept;
// constant codes used in sequences
static const input_code end_code;
static const input_code default_code;
static const input_code not_code;
static const input_code or_code;
static constexpr input_code end_code { DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_END };
static constexpr input_code default_code { DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_DEFAULT };
static constexpr input_code not_code { DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_NOT };
static constexpr input_code or_code { DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_OR };
// constant sequences
static const input_seq empty_seq;
private:
template <unsigned N> void set() noexcept
{
std::fill(std::next(m_code.begin(), N), m_code.end(), end_code);
}
template <unsigned N, typename... T> void set(input_code code_0, T... code_n) noexcept
{
m_code[N] = code_0;
set<N + 1>(code_n...);
}
// internal state
input_code m_code[16];
std::array<input_code, 16> m_code;
};
@ -478,11 +523,6 @@ public:
bool seq_pressed(const input_seq &seq);
s32 seq_axis_value(const input_seq &seq, input_item_class &itemclass);
// input sequence polling
void seq_poll_start(input_item_class itemclass, const input_seq *startseq = nullptr);
bool seq_poll();
const input_seq &seq_poll_final() const { return m_poll_seq; }
// input sequence helpers
input_seq seq_clean(const input_seq &seq) const;
std::string seq_name(const input_seq &seq) const;
@ -503,11 +543,6 @@ private:
// classes
std::array<std::unique_ptr<input_class>, DEVICE_CLASS_MAXIMUM> m_class;
// sequence polling state
input_seq m_poll_seq;
osd_ticks_t m_poll_seq_last_ticks;
input_item_class m_poll_seq_class;
};

View File

@ -2133,7 +2133,7 @@ void ioport_manager::load_config(config_type cfg_type, util::xml::data_node cons
if (seqtype != -1 && seqnode->get_value() != nullptr)
{
if (strcmp(seqnode->get_value(), "NONE") == 0)
newseq[seqtype].set();
newseq[seqtype].reset();
else
machine().input().seq_from_tokens(newseq[seqtype], seqnode->get_value());
}

View File

@ -9,28 +9,31 @@
***************************************************************************/
#include "emu.h"
#include "luaengine.h"
#include "mame.h"
#include "chd.h"
#include "emuopts.h"
#include "mameopts.h"
#include "audit.h"
#include "info.h"
#include "romload.h"
#include "unzip.h"
#include "validity.h"
#include "sound/samples.h"
#include "clifront.h"
#include "xmlfile.h"
#include "media_ident.h"
#include "osdepend.h"
#include "softlist_dev.h"
#include "ui/moptions.h"
#include "audit.h"
#include "infoxml.h"
#include "language.h"
#include "luaengine.h"
#include "mame.h"
#include "media_ident.h"
#include "pluginopts.h"
#include "emuopts.h"
#include "mameopts.h"
#include "romload.h"
#include "softlist_dev.h"
#include "validity.h"
#include "sound/samples.h"
#include "chd.h"
#include "unzip.h"
#include "xmlfile.h"
#include "osdepend.h"
#include <algorithm>
#include <new>
#include <ctype.h>

View File

@ -9,8 +9,8 @@
***************************************************************************/
#include "emu.h"
#include "infoxml.h"
#include "info.h"
#include "mameopts.h"
#include "machine/ram.h"

View File

@ -8,19 +8,17 @@
***************************************************************************/
#ifndef MAME_FRONTEND_MAME_INFO_H
#define MAME_FRONTEND_MAME_INFO_H
#ifndef MAME_FRONTEND_MAME_INFOXML_H
#define MAME_FRONTEND_MAME_INFOXML_H
#pragma once
#include "emuopts.h"
#include <functional>
#include <vector>
class driver_enumerator;
//**************************************************************************
// FUNCTION PROTOTYPES
//**************************************************************************
@ -42,4 +40,4 @@ private:
bool m_dtd;
};
#endif // MAME_FRONTEND_MAME_INFO_H
#endif // MAME_FRONTEND_MAME_INFOXML_H

View File

@ -0,0 +1,144 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb,Aaron Giles
#include "emu.h"
#include "iptseqpoll.h"
#include <cassert>
input_sequence_poller::input_sequence_poller(input_manager &manager) noexcept :
m_manager(manager),
m_sequence(),
m_class(ITEM_CLASS_INVALID),
m_last_ticks(0),
m_modified(false)
{
}
void input_sequence_poller::start(input_item_class itemclass)
{
m_sequence.reset();
do_start(itemclass);
}
void input_sequence_poller::start(input_item_class itemclass, input_seq const &startseq)
{
// grab the starting sequence to append to, and append an OR if it isn't empty
m_sequence = startseq;
if (input_seq::end_code != m_sequence[0])
m_sequence += input_seq::or_code;
do_start(itemclass);
}
bool input_sequence_poller::poll()
{
assert((ITEM_CLASS_SWITCH == m_class) || (ITEM_CLASS_ABSOLUTE == m_class) || (ITEM_CLASS_RELATIVE == m_class));
int const curlen = m_sequence.length();
input_code lastcode = m_sequence[curlen - 1];
input_code newcode;
if (ITEM_CLASS_SWITCH == m_class)
{
// switch case: see if we have a new code to process
newcode = m_manager.poll_switches();
if (INPUT_CODE_INVALID != newcode)
{
// if code is duplicate, toggle the NOT state on the code
if (curlen && (newcode == lastcode))
{
// back up over the existing code
m_sequence.backspace();
// if there was a NOT preceding it, delete it as well, otherwise append a fresh one
if (m_sequence[curlen - 2] == input_seq::not_code)
m_sequence.backspace();
else
m_sequence += input_seq::not_code;
}
}
}
else
{
// absolute/relative case: see if we have an analog change of sufficient amount
bool const has_or = input_seq::or_code == lastcode;
if (has_or)
lastcode = m_sequence[curlen - 2];
newcode = m_manager.poll_axes();
// if the last code doesn't match absolute/relative of this code, ignore the new one
input_item_class const lastclass = lastcode.item_class();
input_item_class const newclass = newcode.item_class();
if (((ITEM_CLASS_ABSOLUTE == lastclass) && (ITEM_CLASS_ABSOLUTE != newclass)) ||
((ITEM_CLASS_RELATIVE == lastclass) && (ITEM_CLASS_RELATIVE != newclass)))
newcode = INPUT_CODE_INVALID;
// if the new code is valid, check for half-axis toggles on absolute controls
if ((INPUT_CODE_INVALID != newcode) && curlen && (ITEM_CLASS_ABSOLUTE == newclass))
{
input_code last_nomodifier = lastcode;
last_nomodifier.set_item_modifier(ITEM_MODIFIER_NONE);
if (newcode == last_nomodifier)
{
// increment the modifier, wrapping back to none
switch (lastcode.item_modifier())
{
case ITEM_MODIFIER_NONE:
newcode.set_item_modifier(ITEM_MODIFIER_POS);
break;
case ITEM_MODIFIER_POS:
newcode.set_item_modifier(ITEM_MODIFIER_NEG);
break;
default:
case ITEM_MODIFIER_NEG:
newcode.set_item_modifier(ITEM_MODIFIER_NONE);
break;
}
// back up over the previous code so we can re-append
if (has_or)
m_sequence.backspace();
m_sequence.backspace();
}
}
}
// if we got a new code to append it, append it and reset the timer
osd_ticks_t const newticks = osd_ticks();
if (INPUT_CODE_INVALID != newcode)
{
m_sequence += newcode;
m_last_ticks = newticks;
m_modified = true;
}
// if we're recorded at least one item and 2/3 of a second has passed, we're done
if (m_last_ticks && ((m_last_ticks + (osd_ticks_per_second() * 2 / 3)) < newticks))
{
m_class = ITEM_CLASS_INVALID;
return true;
}
// return false to indicate we are still polling
return false;
}
void input_sequence_poller::do_start(input_item_class itemclass)
{
assert((ITEM_CLASS_SWITCH == itemclass) || (ITEM_CLASS_ABSOLUTE == itemclass) || (ITEM_CLASS_RELATIVE == itemclass));
// reset the recording count and the clock
m_class = itemclass;
m_last_ticks = 0;
m_modified = false;
// wait for any inputs that are already active to be released
m_manager.reset_polling();
for (input_code dummycode = KEYCODE_ENTER; INPUT_CODE_INVALID != dummycode; )
dummycode = (ITEM_CLASS_SWITCH == itemclass) ? m_manager.poll_switches() : m_manager.poll_axes();
}

View File

@ -0,0 +1,39 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb,Aaron Giles
/***************************************************************************
iptseqpoll.h
Helper for letting the user select input sequences.
***************************************************************************/
#ifndef MAME_FRONTEND_IPTSEQPOLL_H
#define MAME_FRONTEND_IPTSEQPOLL_H
#pragma once
class input_sequence_poller
{
public:
input_sequence_poller(input_manager &manager) noexcept;
void start(input_item_class itemclass);
void start(input_item_class itemclass, input_seq const &startseq);
bool poll();
input_seq const &sequence() const noexcept { return m_sequence; }
bool valid() const noexcept { return m_sequence.is_valid(); }
bool modified() const noexcept { return m_modified; }
private:
void do_start(input_item_class itemclass);
input_manager &m_manager;
input_seq m_sequence;
input_item_class m_class;
osd_ticks_t m_last_ticks;
bool m_modified;
};
#endif // MAME_FRONTEND_IPTSEQPOLL_H

View File

@ -624,6 +624,13 @@ bool lua_engine::menu_callback(const std::string &menu, int index, const std::st
return ret;
}
void lua_engine::set_machine(running_machine *machine)
{
if (!machine || (machine != m_machine))
m_seq_poll.reset();
m_machine = machine;
}
int lua_engine::enumerate_functions(const char *id, std::function<bool(const sol::protected_function &func)> &&callback)
{
int count = 0;
@ -2085,7 +2092,10 @@ void lua_engine::initialize()
input_type.set("seq_to_tokens", [](input_manager &input, sol::user<input_seq> seq) { return input.seq_to_tokens(seq); });
input_type.set("seq_name", [](input_manager &input, sol::user<input_seq> seq) { return input.seq_name(seq); });
input_type.set("seq_clean", [](input_manager &input, sol::user<input_seq> seq) { input_seq cleaned_seq = input.seq_clean(seq); return sol::make_user(cleaned_seq); });
input_type.set("seq_poll_start", [](input_manager &input, const char *cls_string, sol::object seq) {
input_type.set("seq_poll_start", [this](input_manager &input, const char *cls_string, sol::object seq) {
if (!m_seq_poll)
m_seq_poll.reset(new input_sequence_poller(input));
input_item_class cls;
if (!strcmp(cls_string, "switch"))
cls = ITEM_CLASS_SWITCH;
@ -2098,13 +2108,17 @@ void lua_engine::initialize()
else
cls = ITEM_CLASS_INVALID;
input_seq *start = nullptr;
if(seq.is<sol::user<input_seq>>())
start = &seq.as<sol::user<input_seq>>();
input.seq_poll_start(cls, start);
if (seq.is<sol::user<input_seq>>())
m_seq_poll->start(cls, seq.as<sol::user<input_seq>>());
else
m_seq_poll->start(cls);
});
input_type.set("seq_poll", [this](input_manager &input) {
return m_seq_poll->poll();
});
input_type.set("seq_poll_final", [this](input_manager &input) {
return sol::make_user(m_seq_poll->valid() ? m_seq_poll->sequence() : input_seq());
});
input_type.set("seq_poll", &input_manager::seq_poll);
input_type.set("seq_poll_final", [](input_manager &input) { return sol::make_user(input.seq_poll_final()); });
input_type.set("device_classes", sol::property([this](input_manager &input) {
sol::table result = sol().create_table();
for (input_device_class devclass_id = DEVICE_CLASS_FIRST_VALID; devclass_id <= DEVICE_CLASS_LAST_VALID; devclass_id++)

View File

@ -7,22 +7,21 @@
Controls execution of the core MAME system.
***************************************************************************/
#ifndef MAME_FRONTEND_MAME_LUAENGINE_H
#define MAME_FRONTEND_MAME_LUAENGINE_H
#pragma once
#ifndef __EMU_H__
#error Dont include this file directly; include emu.h instead.
#endif
#include "iptseqpoll.h"
#include <condition_variable>
#include <map>
#include <memory>
#if defined(__GNUC__) && (__GNUC__ > 6)
#pragma GCC diagnostic ignored "-Wnoexcept-type"
#endif
#include <map>
#include <condition_variable>
#define SOL_SAFE_USERTYPE
//#define SOL_CHECK_ARGUMENTS
#include "sol2/sol.hpp"
@ -45,7 +44,7 @@ public:
void menu_populate(const std::string &menu, std::vector<std::tuple<std::string, std::string, std::string>> &menu_list);
bool menu_callback(const std::string &menu, int index, const std::string &event);
void set_machine(running_machine *machine) { m_machine = machine; }
void set_machine(running_machine *machine);
std::vector<std::string> &get_menu() { return m_menu; }
void attach_notifiers();
void on_frame_done();
@ -110,11 +109,13 @@ public:
}
sol::state_view &sol() const { return *m_sol_state; }
private:
// internal state
lua_State *m_lua_state;
std::unique_ptr<sol::state_view> m_sol_state;
running_machine *m_machine;
std::unique_ptr<input_sequence_poller> m_seq_poll;
std::vector<std::string> m_menu;

View File

@ -10,7 +10,9 @@
#ifndef MAME_FRONTEND_MEDIA_IDENT_H
#define MAME_FRONTEND_MEDIA_IDENT_H
#include "drivenum.h"
#include "romload.h"
#include <vector>

View File

@ -239,6 +239,9 @@ menu_input::menu_input(mame_ui_manager &mui, render_container &container)
: menu(mui, container)
, data()
, pollingitem(nullptr)
, seq_poll(machine().input())
, errormsg()
, erroritem(nullptr)
, lastitem(nullptr)
, record_next(false)
{
@ -255,12 +258,9 @@ menu_input::~menu_input()
void menu_input::toggle_none_default(input_seq &selected_seq, input_seq &original_seq, const input_seq &selected_defseq)
{
/* if we used to be "none", toggle to the default value */
if (original_seq.length() == 0)
if (original_seq.empty()) // if we used to be "none", toggle to the default value
selected_seq = selected_defseq;
/* otherwise, toggle to "none" */
else
else // otherwise, toggle to "none"
selected_seq.reset();
}
@ -268,8 +268,7 @@ void menu_input::custom_render(void *selectedref, float top, float bottom, float
{
if (pollingitem)
{
const std::string seqname = machine().input().seq_name(
machine().input().seq_poll_final()); // relying on the fact that this exposes the sequence polled so far and has no side effects
const std::string seqname = machine().input().seq_name(seq_poll.sequence());
char const *const text[] = { seqname.c_str() };
draw_text_box(
std::begin(text), std::end(text),
@ -277,6 +276,37 @@ void menu_input::custom_render(void *selectedref, float top, float bottom, float
ui::text_layout::CENTER, ui::text_layout::NEVER, false,
ui().colors().text_color(), ui().colors().background_color(), 1.0f);
}
else
{
if (erroritem && (selectedref != erroritem))
{
errormsg.clear();
erroritem = nullptr;
}
if (erroritem)
{
char const *const text[] = { errormsg.c_str() };
draw_text_box(
std::begin(text), std::end(text),
x1, x2, y2 + ui().box_tb_border(), y2 + bottom,
ui::text_layout::CENTER, ui::text_layout::NEVER, false,
ui().colors().text_color(), UI_RED_COLOR, 1.0f);
}
else if (selectedref)
{
const input_item_data &item = *reinterpret_cast<input_item_data *>(selectedref);
if ((INPUT_TYPE_ANALOG != item.type) && machine().input().seq_pressed(item.seq))
{
char const *const text[] = { _("Pressed") };
draw_text_box(
std::begin(text), std::end(text),
x1, x2, y2 + ui().box_tb_border(), y2 + bottom,
ui::text_layout::CENTER, ui::text_layout::NEVER, false,
ui().colors().text_color(), ui().colors().background_color(), 1.0f);
}
}
}
}
void menu_input::handle()
@ -289,58 +319,82 @@ void menu_input::handle()
if (pollingitem)
{
// if we are polling, handle as a special case
input_item_data *item = pollingitem;
input_item_data *const item = pollingitem;
if (machine().ui_input().pressed(IPT_UI_CANCEL))
{
// if UI_CANCEL is pressed, abort
pollingitem = nullptr;
if (machine().input().seq_poll_final() == init_poll_seq)
if (!seq_poll.modified())
{
// cancelled immediately - toggle between default and none
record_next = false;
toggle_none_default(item->seq, starting_seq, *item->defseq);
seqchangeditem = item;
}
else
{
// entered something before cancelling - abandon change
invalidate = true;
}
}
else if (machine().input().seq_poll())
else if (seq_poll.poll()) // poll again; if finished, update the sequence
{
// poll again; if finished, update the sequence
pollingitem = nullptr;
record_next = true;
item->seq = machine().input().seq_poll_final();
seqchangeditem = item;
if (seq_poll.valid())
{
record_next = true;
item->seq = seq_poll.sequence();
seqchangeditem = item;
}
else
{
// entered invalid sequence - abandon change
invalidate = true;
errormsg = _("Invalid sequence entered");
erroritem = item;
}
}
}
else if (menu_event != nullptr && menu_event->itemref != nullptr)
else if (menu_event && menu_event->itemref)
{
// otherwise, handle the events
input_item_data *item = (input_item_data *)menu_event->itemref;
input_item_data &item = *reinterpret_cast<input_item_data *>(menu_event->itemref);
switch (menu_event->iptkey)
{
case IPT_UI_SELECT: // an item was selected: begin polling
pollingitem = item;
lastitem = item;
starting_seq = item->seq;
machine().input().seq_poll_start((item->type == INPUT_TYPE_ANALOG) ? ITEM_CLASS_ABSOLUTE : ITEM_CLASS_SWITCH, record_next ? &item->seq : nullptr);
init_poll_seq = machine().input().seq_poll_final();
errormsg.clear();
erroritem = nullptr;
pollingitem = &item;
lastitem = &item;
starting_seq = item.seq;
if (record_next)
seq_poll.start((item.type == INPUT_TYPE_ANALOG) ? ITEM_CLASS_ABSOLUTE : ITEM_CLASS_SWITCH, item.seq);
else
seq_poll.start((item.type == INPUT_TYPE_ANALOG) ? ITEM_CLASS_ABSOLUTE : ITEM_CLASS_SWITCH);
invalidate = true;
break;
case IPT_UI_CLEAR: // if the clear key was pressed, reset the selected item
toggle_none_default(item->seq, item->seq, *item->defseq);
errormsg.clear();
erroritem = nullptr;
toggle_none_default(item.seq, item.seq, *item.defseq);
record_next = false;
seqchangeditem = item;
seqchangeditem = &item;
break;
}
// if the selection changed, reset the "record next" flag
if (item != lastitem)
if (&item != lastitem)
{
if (erroritem)
{
errormsg.clear();
erroritem = nullptr;
}
record_next = false;
lastitem = item;
lastitem = &item;
}
}
// if the sequence changed, update it

View File

@ -13,7 +13,9 @@
#pragma once
#include "ui/menu.h"
#include "iptseqpoll.h"
#include <string>
#include <vector>
@ -30,6 +32,7 @@ private:
virtual void handle() override;
};
class menu_input : public menu
{
public:
@ -63,20 +66,23 @@ protected:
void populate_sorted(float &customtop, float &custombottom);
void toggle_none_default(input_seq &selected_seq, input_seq &original_seq, const input_seq &selected_defseq);
data_vector data;
input_item_data * pollingitem;
data_vector data;
input_item_data *pollingitem;
private:
input_item_data * lastitem;
bool record_next;
input_seq starting_seq;
input_seq init_poll_seq;
input_sequence_poller seq_poll;
std::string errormsg;
input_item_data *erroritem;
input_item_data *lastitem;
bool record_next;
input_seq starting_seq;
virtual void custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) override;
virtual void handle() override;
virtual void update_input(input_item_data &seqchangeditem) = 0;
};
class menu_input_general : public menu_input
{
public:
@ -90,6 +96,7 @@ private:
const int group;
};
class menu_input_specific : public menu_input
{
public:

View File

@ -16,7 +16,9 @@
#include "ui/submenu.h"
#include "ui/ui.h"
#include "infoxml.h"
#include "mame.h"
#include "osdnet.h"
#include "mameopts.h"
#include "pluginopts.h"
@ -26,8 +28,6 @@
#include "uiinput.h"
#include "../info.h"
#include <algorithm>
#include <cstring>
#include <fstream>

View File

@ -20,17 +20,17 @@
#include "ui/selsoft.h"
#include "ui/ui.h"
#include "../info.h"
#include "infoxml.h"
#include "luaengine.h"
#include "mame.h"
#include "audit.h"
#include "drivenum.h"
#include "emuopts.h"
#include "mame.h"
#include "rendutil.h"
#include "romload.h"
#include "softlist_dev.h"
#include "uiinput.h"
#include "luaengine.h"
#include <atomic>
#include <condition_variable>