diff options
Diffstat (limited to 'src/frontend/mame/ui/inputmap.cpp')
-rw-r--r-- | src/frontend/mame/ui/inputmap.cpp | 1208 |
1 files changed, 449 insertions, 759 deletions
diff --git a/src/frontend/mame/ui/inputmap.cpp b/src/frontend/mame/ui/inputmap.cpp index eb90215520e..b135780a08f 100644 --- a/src/frontend/mame/ui/inputmap.cpp +++ b/src/frontend/mame/ui/inputmap.cpp @@ -9,145 +9,140 @@ *********************************************************************/ #include "emu.h" +#include "ui/inputmap.h" #include "uiinput.h" #include "ui/ui.h" -#include "ui/menu.h" -#include "ui/inputmap.h" #include <algorithm> namespace ui { -/*************************************************************************** - CONSTANTS -***************************************************************************/ - -#define MAX_PHYSICAL_DIPS 10 -#define MAX_INPUT_PORTS 32 -#define MAX_BITS_PER_PORT 32 - -/* DIP switch rendering parameters */ -#define DIP_SWITCH_HEIGHT 0.05f -#define DIP_SWITCH_SPACING 0.01f -#define SINGLE_TOGGLE_SWITCH_FIELD_WIDTH 0.025f -#define SINGLE_TOGGLE_SWITCH_WIDTH 0.020f -/* make the switch 80% of the width space and 1/2 of the switch height */ -#define PERCENTAGE_OF_HALF_FIELD_USED 0.80f -#define SINGLE_TOGGLE_SWITCH_HEIGHT ((DIP_SWITCH_HEIGHT / 2) * PERCENTAGE_OF_HALF_FIELD_USED) - - /*------------------------------------------------- - menu_input_groups_populate - populate the - input groups menu + menu_input_groups - handle the input groups + menu -------------------------------------------------*/ menu_input_groups::menu_input_groups(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Input Assignments (general)")); } -void menu_input_groups::populate(float &customtop, float &custombottom) +menu_input_groups::~menu_input_groups() { - int player; +} - /* build up the menu */ - item_append(_("User Interface"), "", 0, (void *)(IPG_UI + 1)); - for (player = 0; player < MAX_PLAYERS; player++) +void menu_input_groups::populate() +{ + // build up the menu + item_append(_("User Interface"), 0, (void *)uintptr_t(IPG_UI + 1)); + for (int player = 0; player < MAX_PLAYERS; player++) { - auto s = string_format("Player %d Controls", player + 1); - item_append(s, "", 0, (void *)(uintptr_t)(IPG_PLAYER1 + player + 1)); + auto s = string_format(_("Player %1$d Controls"), player + 1); + item_append(s, 0, (void *)uintptr_t(IPG_PLAYER1 + player + 1)); } - item_append(_("Other Controls"), "", 0, (void *)(uintptr_t)(IPG_OTHER + 1)); + item_append(_("Other Controls"), 0, (void *)uintptr_t(IPG_OTHER + 1)); + item_append(menu_item_type::SEPARATOR); } -menu_input_groups::~menu_input_groups() +bool menu_input_groups::handle(event const *ev) { -} - -/*------------------------------------------------- - menu_input_groups - handle the input groups - menu --------------------------------------------------*/ + if (ev && (ev->iptkey == IPT_UI_SELECT)) + { + menu::stack_push<menu_input_general>( + ui(), + container(), + int(uintptr_t(ev->itemref) - 1), + util::string_format(_("Input Assignments (%1$s)"), ev->item->text())); + } -void menu_input_groups::handle() -{ - /* process the menu */ - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_input_general>(ui(), container(), int((long long)(menu_event->itemref)-1)); + return false; } - /*------------------------------------------------- menu_input_general - handle the general input menu -------------------------------------------------*/ -menu_input_general::menu_input_general(mame_ui_manager &mui, render_container &container, int _group) : menu_input(mui, container) +menu_input_general::menu_input_general(mame_ui_manager &mui, render_container &container, int _group, std::string &&heading) + : menu_input(mui, container) + , group(_group) { - group = _group; + set_heading(std::move(heading)); } -void menu_input_general::populate(float &customtop, float &custombottom) +menu_input_general::~menu_input_general() { - input_item_data *itemlist = nullptr; +} - /* iterate over the input ports and add menu items */ - for (input_type_entry &entry : machine().ioport().types()) +void menu_input_general::menu_activated() +{ + // scripts can change settings out from under us + reset(reset_options::REMEMBER_POSITION); +} - /* add if we match the group and we have a valid name */ - if (entry.group() == group && entry.name() != nullptr && entry.name()[0] != 0) - { - input_seq_type seqtype; +void menu_input_general::populate() +{ + if (data.empty()) + { + assert(!pollingitem); - /* loop over all sequence types */ - for (seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + // iterate over the input ports and add menu items + for (const input_type_entry &entry : machine().ioport().types()) + { + // add if we match the group and we have a valid name + if (entry.group() == group) { - /* build an entry for the standard sequence */ - input_item_data *item = (input_item_data *)m_pool_alloc(sizeof(*item)); - memset(item, 0, sizeof(*item)); - item->ref = &entry; - if(pollingitem && pollingref == &entry && pollingseq == seqtype) - pollingitem = item; - item->seqtype = seqtype; - item->seq = machine().ioport().type_seq(entry.type(), entry.player(), seqtype); - item->defseq = &entry.defseq(seqtype); - item->group = entry.group(); - item->type = ioport_manager::type_is_analog(entry.type()) ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; - item->is_optional = false; - item->name = entry.name(); - item->owner_name = nullptr; - item->next = itemlist; - itemlist = item; - - /* stop after one, unless we're analog */ - if (item->type == INPUT_TYPE_DIGITAL) - break; + std::string name = entry.name(); + if (!name.empty()) + { + // loop over all sequence types + for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + { + // build an entry for the standard sequence + input_item_data &item(data.emplace_back()); + item.ref = &entry; + item.seqtype = seqtype; + item.seq = machine().ioport().type_seq(entry.type(), entry.player(), seqtype); + item.defseq = &entry.defseq(seqtype); + item.group = entry.group(); + item.type = ioport_manager::type_is_analog(entry.type()) ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; + item.is_optional = false; + item.name = name; + item.owner = nullptr; + + // stop after one, unless we're analog + if (item.type == INPUT_TYPE_DIGITAL) + break; + } + } } } - - - // first count the number of items - int numitems = 0; - for (input_item_data const *item = itemlist; item != nullptr; item = item->next) - numitems++; - - // now allocate an array of items and fill it up - std::vector<input_item_data *> itemarray(numitems); - int curitem = numitems; - for (input_item_data *item = itemlist; item != nullptr; item = item->next) - itemarray[--curitem] = item; + } + else + { + for (input_item_data &item : data) + { + const input_type_entry &entry(*reinterpret_cast<const input_type_entry *>(item.ref)); + item.seq = machine().ioport().type_seq(entry.type(), entry.player(), item.seqtype); + } + } // populate the menu in a standard fashion - populate_sorted(std::move(itemarray)); + populate_sorted(); + item_append(menu_item_type::SEPARATOR); } -menu_input_general::~menu_input_general() +void menu_input_general::update_input(input_item_data &seqchangeditem) { + const input_type_entry &entry = *reinterpret_cast<const input_type_entry *>(seqchangeditem.ref); + machine().ioport().set_type_seq(entry.type(), entry.player(), seqchangeditem.seqtype, seqchangeditem.seq); + seqchangeditem.seq = machine().ioport().type_seq(entry.type(), entry.player(), seqchangeditem.seqtype); } + /*------------------------------------------------- menu_input_specific - handle the game-specific input menu @@ -155,115 +150,155 @@ menu_input_general::~menu_input_general() menu_input_specific::menu_input_specific(mame_ui_manager &mui, render_container &container) : menu_input(mui, container) { + set_heading(_("Input Assignments (this system)")); +} + +menu_input_specific::~menu_input_specific() +{ } -void menu_input_specific::populate(float &customtop, float &custombottom) +void menu_input_specific::menu_activated() { - input_item_data *itemlist = nullptr; + // scripts can change settings out from under us + assert(!pollingitem); + data.clear(); + reset(reset_options::REMEMBER_POSITION); +} - /* iterate over the input ports and add menu items */ - for (auto &port : machine().ioport().ports()) +void menu_input_specific::populate() +{ + if (data.empty()) { - for (ioport_field &field : port.second->fields()) - { - ioport_type_class type_class = field.type_class(); + assert(!pollingitem); - /* add if we match the group and we have a valid name */ - if (field.enabled() && (type_class == INPUT_CLASS_CONTROLLER || type_class == INPUT_CLASS_MISC || type_class == INPUT_CLASS_KEYBOARD)) + // iterate over the input ports and add menu items + for (auto &port : machine().ioport().ports()) + { + for (ioport_field &field : port.second->fields()) { - /* loop over all sequence types */ - for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + const ioport_type_class type_class = field.type_class(); + + // add if it's enabled and it's a system-specific class + if (field.enabled() && (type_class == INPUT_CLASS_CONTROLLER || type_class == INPUT_CLASS_MISC || type_class == INPUT_CLASS_KEYBOARD)) { - /* build an entry for the standard sequence */ - input_item_data *item = (input_item_data *)m_pool_alloc(sizeof(*item)); - memset(item, 0, sizeof(*item)); - item->ref = &field; - item->seqtype = seqtype; - if(pollingitem && pollingref == item->ref && pollingseq == seqtype) - pollingitem = item; - item->seq = field.seq(seqtype); - item->defseq = &field.defseq(seqtype); - item->group = machine().ioport().type_group(field.type(), field.player()); - item->type = field.is_analog() ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; - item->is_optional = field.optional(); - item->name = field.name(); - item->owner_name = field.device().tag(); - item->next = itemlist; - itemlist = item; - - /* stop after one, unless we're analog */ - if (item->type == INPUT_TYPE_DIGITAL) - break; + // loop over all sequence types + for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + { + // build an entry for the standard sequence + input_item_data &item(data.emplace_back()); + item.ref = &field; + item.seqtype = seqtype; + item.seq = field.seq(seqtype); + item.defseq = &field.defseq(seqtype); + item.group = machine().ioport().type_group(field.type(), field.player()); + item.type = field.is_analog() ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; + item.is_optional = field.optional(); + item.name = field.name(); + item.owner = &field.device(); + + // stop after one, unless we're analog + if (item.type == INPUT_TYPE_DIGITAL) + break; + } } } } - } - // first count the number of items - int numitems = 0; - for (input_item_data const *item = itemlist; item != nullptr; item = item->next) - numitems++; - - // now allocate an array of items and fill it up - std::vector<input_item_data *> itemarray(numitems); - int curitem = 0; - for (input_item_data *item = itemlist; item != nullptr; item = item->next) - itemarray[curitem++] = item; - - // sort it - std::sort(itemarray.begin(), itemarray.end(), [](const input_item_data *i1, const input_item_data *i2) { - int cmp = strcmp(i1->owner_name, i2->owner_name); - if (cmp < 0) - return true; - if (cmp > 0) - return false; - if (i1->group < i2->group) - return true; - if (i1->group > i2->group) - return false; - const ioport_field &field1 = *reinterpret_cast<const ioport_field *>(i1->ref); - const ioport_field &field2 = *reinterpret_cast<const ioport_field *>(i2->ref); - if (field1.type() < field2.type()) - return true; - if (field1.type() > field2.type()) - return false; - std::vector<char32_t> codes1 = field1.keyboard_codes(0); - std::vector<char32_t> codes2 = field2.keyboard_codes(0); - if (!codes1.empty() && (codes2.empty() || codes1[0] < codes2[0])) - return true; - if (!codes2.empty() && (codes1.empty() || codes1[0] > codes2[0])) - return false; - cmp = strcmp(i1->name, i2->name); - if (cmp < 0) - return true; - if (cmp > 0) - return false; - return i1->type < i2->type; - }); + // sort it + std::sort( + data.begin(), + data.end(), + [] (const input_item_data &i1, const input_item_data &i2) + { + int cmp = strcmp(i1.owner->tag(), i2.owner->tag()); + if (cmp < 0) + return true; + if (cmp > 0) + return false; + if (i1.group < i2.group) + return true; + if (i1.group > i2.group) + return false; + const ioport_field &field1 = *reinterpret_cast<const ioport_field *>(i1.ref); + const ioport_field &field2 = *reinterpret_cast<const ioport_field *>(i2.ref); + if (field1.type() < field2.type()) + return true; + if (field1.type() > field2.type()) + return false; + std::vector<char32_t> codes1 = field1.keyboard_codes(0); + std::vector<char32_t> codes2 = field2.keyboard_codes(0); + if (!codes1.empty() && (codes2.empty() || codes1[0] < codes2[0])) + return true; + if (!codes2.empty() && (codes1.empty() || codes1[0] > codes2[0])) + return false; + cmp = i1.name.compare(i2.name); + if (cmp < 0) + return true; + if (cmp > 0) + return false; + return i1.type < i2.type; + }); + } + else + { + for (input_item_data &item : data) + { + const ioport_field &field(*reinterpret_cast<const ioport_field *>(item.ref)); + item.seq = field.seq(item.seqtype); + } + } // populate the menu in a standard fashion - populate_sorted(std::move(itemarray)); + if (!data.empty()) + populate_sorted(); + else + item_append(_("[no assignable inputs are enabled]"), FLAG_DISABLE, nullptr); + + item_append(menu_item_type::SEPARATOR); } -menu_input_specific::~menu_input_specific() +void menu_input_specific::update_input(input_item_data &seqchangeditem) { + ioport_field::user_settings settings; + + // yeah, the const_cast is naughty, but we know we stored a non-const reference in it + ioport_field const &field(*reinterpret_cast<ioport_field const *>(seqchangeditem.ref)); + field.get_user_settings(settings); + settings.seq[seqchangeditem.seqtype] = seqchangeditem.seq; + if (seqchangeditem.seq.is_default()) + settings.cfg[seqchangeditem.seqtype].clear(); + else if (!seqchangeditem.seq.length()) + settings.cfg[seqchangeditem.seqtype] = "NONE"; + else + settings.cfg[seqchangeditem.seqtype] = machine().input().seq_to_tokens(seqchangeditem.seq); + const_cast<ioport_field &>(field).set_user_settings(settings); + seqchangeditem.seq = field.seq(seqchangeditem.seqtype); } + /*------------------------------------------------- menu_input - display a menu for inputs -------------------------------------------------*/ -menu_input::menu_input(mame_ui_manager &mui, render_container &container) : menu(mui, container), record_next(false) + +menu_input::menu_input(mame_ui_manager &mui, render_container &container) + : menu(mui, container) + , data() + , pollingitem(nullptr) + , seq_poll() + , errormsg() + , erroritem(nullptr) + , lastitem(nullptr) + , record_next(false) + , modified_ticks(0) { - lastitem = nullptr; - pollingitem = nullptr; - pollingref = nullptr; - pollingseq = SEQ_TYPE_STANDARD; + set_process_flags(PROCESS_LR_ALWAYS); } menu_input::~menu_input() { } + /*------------------------------------------------- toggle_none_default - toggle between "NONE" and the default item @@ -271,665 +306,320 @@ 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) - selected_seq = selected_defseq; - - /* otherwise, toggle to "none" */ - else + if (original_seq.empty()) // if we used to be "none", toggle to the default value + selected_seq.set_default(); + else // otherwise, toggle to "none" selected_seq.reset(); } -void menu_input::handle() -{ - input_item_data *seqchangeditem = nullptr; - const event *menu_event; - int invalidate = false; - - /* process the menu */ - menu_event = process((pollingitem != nullptr) ? PROCESS_NOKEYS : 0); - - /* if we are polling, handle as a special case */ - if (pollingitem != nullptr) - { - input_item_data *item = pollingitem; - - /* if UI_CANCEL is pressed, abort */ - if (machine().ui_input().pressed(IPT_UI_CANCEL)) - { - pollingitem = nullptr; - record_next = false; - toggle_none_default(item->seq, starting_seq, *item->defseq); - seqchangeditem = item; - } - - /* poll again; if finished, update the sequence */ - if (machine().input().seq_poll()) - { - pollingitem = nullptr; - record_next = true; - item->seq = machine().input().seq_poll_final(); - seqchangeditem = item; - } - } - /* otherwise, handle the events */ - else if (menu_event != nullptr && menu_event->itemref != nullptr) - { - input_item_data *item = (input_item_data *)menu_event->itemref; - switch (menu_event->iptkey) - { - /* an item was selected: begin polling */ - case IPT_UI_SELECT: - 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); - invalidate = true; - break; - - /* if the clear key was pressed, reset the selected item */ - case IPT_UI_CLEAR: - toggle_none_default(item->seq, item->seq, *item->defseq); - record_next = false; - seqchangeditem = item; - break; - } - - /* if the selection changed, reset the "record next" flag */ - if (item != lastitem) - record_next = false; - lastitem = item; - } - - /* if the sequence changed, update it */ - if (seqchangeditem != nullptr) - { - update_input(seqchangeditem); - - /* invalidate the menu to force an update */ - invalidate = true; - } - - /* if the menu is invalidated, clear it now */ - if (invalidate) - { - pollingref = nullptr; - if (pollingitem != nullptr) - { - pollingref = pollingitem->ref; - pollingseq = pollingitem->seqtype; - } - reset(reset_options::REMEMBER_POSITION); - } -} - -void menu_input_general::update_input(struct input_item_data *seqchangeditem) -{ - const input_type_entry *entry = (const input_type_entry *)seqchangeditem->ref; - machine().ioport().set_type_seq(entry->type(), entry->player(), seqchangeditem->seqtype, seqchangeditem->seq); -} - -void menu_input_specific::update_input(struct input_item_data *seqchangeditem) +void menu_input::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - ioport_field::user_settings settings; + menu::recompute_metrics(width, height, aspect); - ((ioport_field *)seqchangeditem->ref)->get_user_settings(settings); - settings.seq[seqchangeditem->seqtype] = seqchangeditem->seq; - ((ioport_field *)seqchangeditem->ref)->set_user_settings(settings); + // leave space for showing the input sequence below the menu + set_custom_space(0.0F, 2.0F * line_height() + 3.0F * tb_border()); } -//------------------------------------------------- -// populate_sorted - take a sorted list of -// input_item_data objects and build up the -// menu from them -//------------------------------------------------- - -void menu_input::populate_sorted(std::vector<input_item_data *> &&itemarray) +void menu_input::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - const char *nameformat[INPUT_TYPE_TOTAL] = { nullptr }; - std::string subtext; - std::string prev_owner; - bool first_entry = true; - - /* create a mini lookup table for name format based on type */ - nameformat[INPUT_TYPE_DIGITAL] = "%s"; - nameformat[INPUT_TYPE_ANALOG] = "%s Analog"; - nameformat[INPUT_TYPE_ANALOG_INC] = "%s Analog Inc"; - nameformat[INPUT_TYPE_ANALOG_DEC] = "%s Analog Dec"; - - /* build the menu */ - for (input_item_data *item : itemarray) + if (pollingitem) { - uint32_t flags = 0; - - /* generate the name of the item itself, based off the base name and the type */ - assert(nameformat[item->type] != nullptr); - - if (item->owner_name && strcmp(item->owner_name, prev_owner.c_str()) != 0) + 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), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); + } + else + { + if (erroritem && (selectedref != erroritem)) { - if (first_entry) - first_entry = false; - else - item_append(menu_item_type::SEPARATOR); - item_append(string_format("[root%s]", item->owner_name), "", 0, nullptr); - prev_owner.assign(item->owner_name); + errormsg.clear(); + erroritem = nullptr; } - std::string text = string_format(nameformat[item->type], item->name); - if (item->is_optional) - text = "(" + text + ")"; - - /* if we're polling this item, use some spaces with left/right arrows */ - if (pollingref == item->ref && pollingseq == item->seqtype) + if (erroritem) { - subtext.assign(" "); - flags |= FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; + char const *const text[] = { errormsg.c_str() }; + draw_text_box( + std::begin(text), std::end(text), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), UI_RED_COLOR); } - - /* otherwise, generate the sequence name and invert it if different from the default */ - else + else if (selectedref) { - subtext = machine().input().seq_name(item->seq); - flags |= (item->seq != *item->defseq) ? FLAG_INVERT : 0; + 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), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); + } + else + { + char const *const text[] = { + record_next ? appendprompt.c_str() : assignprompt.c_str(), + (!item.seq.empty() || item.defseq->empty()) ? clearprompt.c_str() : defaultprompt.c_str() }; + draw_text_box( + std::begin(text), std::end(text), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); + } } - - /* add the item */ - item_append(text, subtext, flags, item); } } - -/*------------------------------------------------- - menu_settings_dip_switches - handle the DIP - switches menu --------------------------------------------------*/ - -menu_settings_dip_switches::menu_settings_dip_switches(mame_ui_manager &mui, render_container &container) : menu_settings(mui, container, IPT_DIPSWITCH) +bool menu_input::handle(event const *ev) { -} - -menu_settings_dip_switches::~menu_settings_dip_switches() -{ -} - -/*------------------------------------------------- - menu_settings_driver_config - handle the - driver config menu --------------------------------------------------*/ - -menu_settings_driver_config::menu_settings_driver_config(mame_ui_manager &mui, render_container &container) : menu_settings(mui, container, IPT_CONFIG) -{ -} - -menu_settings_driver_config::~menu_settings_driver_config() -{ -} - -/*------------------------------------------------- - menu_settings_common - handle one of the - switches menus --------------------------------------------------*/ + input_item_data *seqchangeditem = nullptr; + bool invalidate = false; + bool redraw = false; -void menu_settings::handle() -{ // process the menu - const event *menu_event = process(0); - - // handle events - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (pollingitem) { - // reset - if ((uintptr_t)menu_event->itemref == 1) + // if we are polling, handle as a special case + input_item_data *const item = pollingitem; + + // prevent race condition between ui_input().pressed() and poll() + if (modified_ticks == 0 && seq_poll->modified()) + modified_ticks = osd_ticks(); + + if (machine().ui_input().pressed(IPT_UI_CANCEL)) { - if (menu_event->iptkey == IPT_UI_SELECT) - machine().schedule_hard_reset(); + // if UI_CANCEL is pressed, abort and abandon changes + pollingitem = nullptr; + set_process_flags(PROCESS_LR_ALWAYS); + invalidate = true; + seq_poll.reset(); + machine().ui_input().reset(); } - // actual settings - else + else if (seq_poll->poll()) // poll again; if finished, update the sequence { - ioport_field *field = (ioport_field *)menu_event->itemref; - ioport_field::user_settings settings; - int changed = false; - - switch (menu_event->iptkey) + pollingitem = nullptr; + set_process_flags(PROCESS_LR_ALWAYS); + if (seq_poll->valid()) + { + record_next = true; + item->seq = seq_poll->sequence(); + seqchangeditem = item; + } + else { - /* if selected, reset to default value */ - case IPT_UI_SELECT: - field->get_user_settings(settings); - settings.value = field->defvalue(); - field->set_user_settings(settings); - changed = true; - break; - - /* left goes to previous setting */ - case IPT_UI_LEFT: - field->select_previous_setting(); - changed = true; - break; - - /* right goes to next setting */ - case IPT_UI_RIGHT: - field->select_next_setting(); - changed = true; - break; + // entered invalid sequence - abandon change + invalidate = true; + errormsg = _("Invalid combination entered"); + erroritem = item; } - - /* if anything changed, rebuild the menu, trying to stay on the same field */ - if (changed) - reset(reset_options::REMEMBER_REF); + seq_poll.reset(); + machine().ui_input().reset(); + } + else + { + // always redraw to ensure it updates as soon as possible in response to changes + redraw = true; } } -} - - -/*------------------------------------------------- - menu_settings_populate - populate one of the - switches menus --------------------------------------------------*/ - -menu_settings::menu_settings(mame_ui_manager &mui, render_container &container, uint32_t _type) : menu(mui, container), diplist(nullptr), dipcount(0) -{ - type = _type; -} + else if (ev && ev->itemref) + { + // otherwise, handle the events + input_item_data &item = *reinterpret_cast<input_item_data *>(ev->itemref); + input_item_data *newsel = &item; + switch (ev->iptkey) + { + case IPT_UI_SELECT: // an item was selected: begin polling + set_process_flags(PROCESS_NOKEYS); + errormsg.clear(); + erroritem = nullptr; + modified_ticks = 0; + pollingitem = &item; + lastitem = &item; + starting_seq = item.seq; + if (INPUT_TYPE_ANALOG == item.type) + seq_poll.reset(new axis_sequence_poller(machine().input())); + else + seq_poll.reset(new switch_sequence_poller(machine().input())); + if (record_next) + seq_poll->start(item.seq); + else + seq_poll->start(); + invalidate = true; + break; + + case IPT_UI_CLEAR: // if the clear key was pressed, reset the selected item + errormsg.clear(); + erroritem = nullptr; + toggle_none_default(item.seq, item.seq, *item.defseq); + record_next = false; + seqchangeditem = &item; + break; -void menu_settings::populate(float &customtop, float &custombottom) -{ - dip_descriptor **diplist_tailptr; - std::string prev_owner; - bool first_entry = true; - - /* reset the dip switch tracking */ - dipcount = 0; - diplist = nullptr; - diplist_tailptr = &diplist; - - /* loop over input ports and set up the current values */ - for (auto &port : machine().ioport().ports()) - for (ioport_field &field : port.second->fields()) - if (field.type() == type && field.enabled()) + case IPT_UI_PREV_GROUP: { - if (!field.settings().empty()) + auto current = std::distance(data.data(), &item); + bool found_break = false; + while (0 < current) { - uint32_t flags = 0; - - /* set the left/right flags appropriately */ - if (field.has_previous_setting()) - flags |= FLAG_LEFT_ARROW; - if (field.has_next_setting()) - flags |= FLAG_RIGHT_ARROW; - - /* add the menu item */ - if (strcmp(field.device().tag(), prev_owner.c_str()) != 0) + if (!found_break) { - if (first_entry) - first_entry = false; - else - item_append(menu_item_type::SEPARATOR); - string_format("[root%s]", field.device().tag()); - item_append(string_format("[root%s]", field.device().tag()), "", 0, nullptr); - prev_owner.assign(field.device().tag()); + if (data[--current].owner != item.owner) + found_break = true; + } + else if (data[current].owner != data[current - 1].owner) + { + newsel = &data[current]; + set_selection(newsel); + set_top_line(selected_index() - 1); + break; + } + else + { + --current; + } + if (found_break && !current) + { + newsel = &data[current]; + set_selection(newsel); + set_top_line(selected_index() - 1); + break; } - - item_append(field.name(), field.setting_name(), flags, (void *)&field); } + } + break; - /* for DIP switches, build up the model */ - if (type == IPT_DIPSWITCH && !field.diplocations().empty()) + case IPT_UI_NEXT_GROUP: + { + auto current = std::distance(data.data(), &item); + while (data.size() > ++current) { - ioport_field::user_settings settings; - uint32_t accummask = field.mask(); - - /* get current settings */ - field.get_user_settings(settings); - - /* iterate over each bit in the field */ - for (const ioport_diplocation &diploc : field.diplocations()) + if (data[current].owner != item.owner) { - uint32_t mask = accummask & ~(accummask - 1); - dip_descriptor *dip; - - /* find the matching switch name */ - for (dip = diplist; dip != nullptr; dip = dip->next) - if (dip->owner == &field.device() && strcmp(dip->name, diploc.name()) == 0) - break; - - /* allocate new if none */ - if (dip == nullptr) - { - dip = (dip_descriptor *)m_pool_alloc(sizeof(*dip)); - dip->next = nullptr; - dip->name = diploc.name(); - dip->owner = &field.device(); - dip->mask = dip->state = 0; - *diplist_tailptr = dip; - diplist_tailptr = &dip->next; - dipcount++; - } - - /* apply the bits */ - dip->mask |= 1 << (diploc.number() - 1); - if (((settings.value & mask) != 0 && !diploc.inverted()) || ((settings.value & mask) == 0 && diploc.inverted())) - dip->state |= 1 << (diploc.number() - 1); - - /* clear the relevant bit in the accumulated mask */ - accummask &= ~mask; + newsel = &data[current]; + set_selection(newsel); + set_top_line(selected_index() - 1); + break; } } } - if (type == IPT_DIPSWITCH) - custombottom = dipcount ? dipcount * (DIP_SWITCH_HEIGHT + DIP_SWITCH_SPACING) + DIP_SWITCH_SPACING : 0; - - item_append(menu_item_type::SEPARATOR); - item_append(_("Reset"), "", 0, (void *)1); -} - -menu_settings::~menu_settings() -{ -} - -/*------------------------------------------------- - menu_settings_custom_render - perform our special - rendering --------------------------------------------------*/ - -void menu_settings_dip_switches::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) -{ - // catch if no diploc has to be drawn - if (bottom == 0) - return; - - // add borders - y1 = y2 + ui().box_tb_border(); - y2 = y1 + bottom; - - // draw extra menu area - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - y1 += (float)DIP_SWITCH_SPACING; - - // iterate over DIP switches - for (dip_descriptor *dip = diplist; dip != nullptr; dip = dip->next) - { - uint32_t selectedmask = 0; + break; + } - // determine the mask of selected bits - if ((uintptr_t)selectedref != 1) + // if the selection changed, reset the "record next" flag + if (newsel != lastitem) { - ioport_field *field = (ioport_field *)selectedref; - - if (field != nullptr && !field->diplocations().empty()) - for (const ioport_diplocation &diploc : field->diplocations()) - if (dip->owner == &field->device() && strcmp(dip->name, diploc.name()) == 0) - selectedmask |= 1 << (diploc.number() - 1); + if (erroritem) + { + errormsg.clear(); + erroritem = nullptr; + } + record_next = false; + lastitem = &item; + redraw = true; } - // draw one switch - custom_render_one(x1, y1, x2, y1 + DIP_SWITCH_HEIGHT, dip, selectedmask); - y1 += (float)(DIP_SWITCH_SPACING + DIP_SWITCH_HEIGHT); + // flip between set and append + // not very discoverable, but with the prompt it isn't completely opaque + if ((IPT_UI_LEFT == ev->iptkey) || (IPT_UI_RIGHT == ev->iptkey)) + { + if (erroritem) + { + errormsg.clear(); + erroritem = nullptr; + } + else if (record_next || !item.seq.empty()) + { + record_next = !record_next; + } + redraw = true; + } } -} - - -/*------------------------------------------------- - menu_settings_custom_render_one - draw a single - DIP switch --------------------------------------------------*/ -void menu_settings_dip_switches::custom_render_one(float x1, float y1, float x2, float y2, const dip_descriptor *dip, uint32_t selectedmask) -{ - float switch_field_width = SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * container().manager().ui_aspect(); - float switch_width = SINGLE_TOGGLE_SWITCH_WIDTH * container().manager().ui_aspect(); - int numtoggles, toggle; - float switch_toggle_gap; - float y1_off, y1_on; - - /* determine the number of toggles in the DIP */ - numtoggles = 32 - count_leading_zeros(dip->mask); - - /* center based on the number of switches */ - x1 += (x2 - x1 - numtoggles * switch_field_width) / 2; - - /* draw the dip switch name */ - ui().draw_text_full(container(), - dip->name, - 0, - y1 + (DIP_SWITCH_HEIGHT - ui().target_font_height()) / 2, - x1 - ui().get_string_width(" "), - ui::text_layout::RIGHT, - ui::text_layout::NEVER, - mame_ui_manager::NORMAL, - ui().colors().text_color(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA), - nullptr , - nullptr); - - /* compute top and bottom for on and off positions */ - switch_toggle_gap = ((DIP_SWITCH_HEIGHT/2) - SINGLE_TOGGLE_SWITCH_HEIGHT)/2; - y1_off = y1 + UI_LINE_WIDTH + switch_toggle_gap; - y1_on = y1 + DIP_SWITCH_HEIGHT/2 + switch_toggle_gap; - - /* iterate over toggles */ - for (toggle = 0; toggle < numtoggles; toggle++) + // if the sequence changed, update it + if (seqchangeditem) { - float innerx1; + update_input(*seqchangeditem); - /* first outline the switch */ - ui().draw_outlined_box(container(), x1, y1, x1 + switch_field_width, y2, ui().colors().background_color()); - - /* compute x1/x2 for the inner filled in switch */ - innerx1 = x1 + (switch_field_width - switch_width) / 2; + // invalidate the menu to force an update + invalidate = true; + } - /* see if the switch is actually used */ - if (dip->mask & (1 << toggle)) - { - float innery1 = (dip->state & (1 << toggle)) ? y1_on : y1_off; - container().add_rect(innerx1, innery1, innerx1 + switch_width, innery1 + SINGLE_TOGGLE_SWITCH_HEIGHT, - (selectedmask & (1 << toggle)) ? ui().colors().dipsw_color() : ui().colors().text_color(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - } - else - { - container().add_rect(innerx1, y1_off, innerx1 + switch_width, y1_on + SINGLE_TOGGLE_SWITCH_HEIGHT, - ui().colors().unavailable_color(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - } + // if the menu is invalidated, clear it now + if (invalidate) + reset(reset_options::REMEMBER_POSITION); - /* advance to the next switch */ - x1 += switch_field_width; - } + return redraw && !invalidate; } -/*------------------------------------------------- - menu_analog - handle the analog settings menu --------------------------------------------------*/ +//------------------------------------------------- +// populate_sorted - take a sorted list of +// input_item_data objects and build up the +// menu from them +//------------------------------------------------- -void menu_analog::handle() +void menu_input::populate_sorted() { - /* process the menu */ - const event *menu_event = process(PROCESS_LR_REPEAT); + const char *nameformat[INPUT_TYPE_TOTAL] = { nullptr }; - /* handle events */ - if (menu_event != nullptr && menu_event->itemref != nullptr) + // create a mini lookup table for name format based on type + nameformat[INPUT_TYPE_DIGITAL] = "%1$s"; + nameformat[INPUT_TYPE_ANALOG] = _("input-name", "%1$s Analog"); + nameformat[INPUT_TYPE_ANALOG_INC] = _("input-name", "%1$s Analog Inc"); + nameformat[INPUT_TYPE_ANALOG_DEC] = _("input-name", "%1$s Analog Dec"); + + // build the menu + std::string text, subtext; + const device_t *prev_owner = nullptr; + for (input_item_data &item : data) { - analog_item_data *data = (analog_item_data *)menu_event->itemref; - int newval = data->cur; + // generate the name of the item itself, based off the base name and the type + assert(nameformat[item.type] != nullptr); - switch (menu_event->iptkey) + if (item.owner && (item.owner != prev_owner)) { - /* if selected, reset to default value */ - case IPT_UI_SELECT: - newval = data->defvalue; - break; - - /* left decrements */ - case IPT_UI_LEFT: - newval -= machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; - break; - - /* right increments */ - case IPT_UI_RIGHT: - newval += machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; - break; + if (item.owner->owner()) + item_append(string_format(_("%1$s [root%2$s]"), item.owner->type().fullname(), item.owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + else + item_append(string_format(_("[root%1$s]"), item.owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + prev_owner = item.owner; } - /* clamp to range */ - if (newval < data->min) - newval = data->min; - if (newval > data->max) - newval = data->max; + text = string_format(nameformat[item.type], item.name); + if (item.is_optional) + text = "(" + text + ")"; - /* if things changed, update */ - if (newval != data->cur) + uint32_t flags = 0; + if (&item == pollingitem) { - ioport_field::user_settings settings; - - /* get the settings and set the new value */ - data->field->get_user_settings(settings); - switch (data->type) - { - case ANALOG_ITEM_KEYSPEED: settings.delta = newval; break; - case ANALOG_ITEM_CENTERSPEED: settings.centerdelta = newval; break; - case ANALOG_ITEM_REVERSE: settings.reverse = newval; break; - case ANALOG_ITEM_SENSITIVITY: settings.sensitivity = newval; break; - } - data->field->set_user_settings(settings); - - /* rebuild the menu */ - reset(reset_options::REMEMBER_POSITION); + // if we're polling this item, use some spaces with left/right arrows + subtext = " "; + flags |= FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; + } + else + { + // otherwise, generate the sequence name and invert it if different from the default + subtext = machine().input().seq_name(item.seq); + flags |= (item.seq != *item.defseq) ? FLAG_INVERT : 0; } - } -} - - -/*------------------------------------------------- - menu_analog_populate - populate the analog - settings menu --------------------------------------------------*/ - -menu_analog::menu_analog(mame_ui_manager &mui, render_container &container) : menu(mui, container) -{ -} - -void menu_analog::populate(float &customtop, float &custombottom) -{ - std::string prev_owner; - bool first_entry = true; - - /* loop over input ports and add the items */ - for (auto &port : machine().ioport().ports()) - for (ioport_field &field : port.second->fields()) - if (field.is_analog() && field.enabled()) - { - ioport_field::user_settings settings; - int use_autocenter = false; - int type; - - /* based on the type, determine if we enable autocenter */ - switch (field.type()) - { - case IPT_POSITIONAL: - case IPT_POSITIONAL_V: - if (field.analog_wraps()) - break; - - case IPT_AD_STICK_X: - case IPT_AD_STICK_Y: - case IPT_AD_STICK_Z: - case IPT_PADDLE: - case IPT_PADDLE_V: - case IPT_PEDAL: - case IPT_PEDAL2: - case IPT_PEDAL3: - use_autocenter = true; - break; - - default: - break; - } - - /* get the user settings */ - field.get_user_settings(settings); - /* iterate over types */ - for (type = 0; type < ANALOG_ITEM_COUNT; type++) - if (type != ANALOG_ITEM_CENTERSPEED || use_autocenter) - { - analog_item_data *data; - uint32_t flags = 0; - std::string text; - std::string subtext; - if (strcmp(field.device().tag(), prev_owner.c_str()) != 0) - { - if (first_entry) - first_entry = false; - else - item_append(menu_item_type::SEPARATOR); - item_append(string_format("[root%s]", field.device().tag()), "", 0, nullptr); - prev_owner.assign(field.device().tag()); - } - - /* allocate a data item for tracking what this menu item refers to */ - data = (analog_item_data *)m_pool_alloc(sizeof(*data)); - data->field = &field; - data->type = type; - - /* determine the properties of this item */ - switch (type) - { - default: - case ANALOG_ITEM_KEYSPEED: - text = string_format("%s Digital Speed", field.name()); - subtext = string_format("%d", settings.delta); - data->min = 0; - data->max = 255; - data->cur = settings.delta; - data->defvalue = field.delta(); - break; - - case ANALOG_ITEM_CENTERSPEED: - text = string_format("%s Autocenter Speed", field.name()); - subtext = string_format("%d", settings.centerdelta); - data->min = 0; - data->max = 255; - data->cur = settings.centerdelta; - data->defvalue = field.centerdelta(); - break; - - case ANALOG_ITEM_REVERSE: - text = string_format("%s Reverse", field.name()); - subtext.assign(settings.reverse ? "On" : "Off"); - data->min = 0; - data->max = 1; - data->cur = settings.reverse; - data->defvalue = field.analog_reverse(); - break; - - case ANALOG_ITEM_SENSITIVITY: - text = string_format("%s Sensitivity", field.name()); - subtext = string_format("%d", settings.sensitivity); - data->min = 1; - data->max = 255; - data->cur = settings.sensitivity; - data->defvalue = field.sensitivity(); - break; - } - - /* put on arrows */ - if (data->cur > data->min) - flags |= FLAG_LEFT_ARROW; - if (data->cur < data->max) - flags |= FLAG_RIGHT_ARROW; - - /* append a menu item */ - item_append(std::move(text), std::move(subtext), flags, data); - } - } -} + // add the item + item_append(std::move(text), std::move(subtext), flags, &item); + } -menu_analog::~menu_analog() -{ + // pre-format messages + assignprompt = util::string_format(_("Press %1$s to set\n"), ui().get_general_input_setting(IPT_UI_SELECT)); + appendprompt = util::string_format(_("Press %1$s to append\n"), ui().get_general_input_setting(IPT_UI_SELECT)); + clearprompt = util::string_format(_("Press %1$s to clear\n"), ui().get_general_input_setting(IPT_UI_CLEAR)); + defaultprompt = util::string_format(_("Press %1$s to restore default\n"), ui().get_general_input_setting(IPT_UI_CLEAR)); } } // namespace ui |