diff options
author | npwoods <npwoods@alumni.cmu.edu> | 2017-06-24 22:48:13 -0400 |
---|---|---|
committer | Vas Crabb <cuavas@users.noreply.github.com> | 2017-06-25 12:48:13 +1000 |
commit | 1b28df0b39225c3017e7bc843c14b58f548162ac (patch) | |
tree | 7ceec13527959f1631fd2d6d9d1dc9eb3f8f700c | |
parent | 69828a8ff9dfe5e56ac00accfddc991f5dce7d61 (diff) |
Reintroduced a state save menu, take two (#2358)
* This turns the current state save feature to a menu; one can still press 0-9, but you can also browse a menu when loading and saving state
* Fixed some issues requested by Vas Crabb
* Updated state menu to support arbitrary character slots
* WIP, transitioned 'entry_char' to std::string
Still need to address Joystick and FR keyboard concerns
* Reimplemented state save support with joystick buttons
* Changed the state menu to be "code driven" rather than "character driven"
When the menu is displayed, it will look at the filenames, and translate them to a visual representation as per the user's locale
* Vas feedback
-rw-r--r-- | scripts/src/mame/frontend.lua | 2 | ||||
-rw-r--r-- | src/frontend/mame/ui/state.cpp | 423 | ||||
-rw-r--r-- | src/frontend/mame/ui/state.h | 96 | ||||
-rw-r--r-- | src/frontend/mame/ui/ui.cpp | 123 | ||||
-rw-r--r-- | src/frontend/mame/ui/ui.h | 1 |
5 files changed, 527 insertions, 118 deletions
diff --git a/scripts/src/mame/frontend.lua b/scripts/src/mame/frontend.lua index 9e0144916a4..ac2746d1429 100644 --- a/scripts/src/mame/frontend.lua +++ b/scripts/src/mame/frontend.lua @@ -159,6 +159,8 @@ files { MAME_DIR .. "src/frontend/mame/ui/sndmenu.cpp", MAME_DIR .. "src/frontend/mame/ui/sndmenu.h", MAME_DIR .. "src/frontend/mame/ui/starimg.ipp", + MAME_DIR .. "src/frontend/mame/ui/state.cpp", + MAME_DIR .. "src/frontend/mame/ui/state.h", MAME_DIR .. "src/frontend/mame/ui/toolbar.ipp", MAME_DIR .. "src/frontend/mame/ui/utils.cpp", MAME_DIR .. "src/frontend/mame/ui/utils.h", diff --git a/src/frontend/mame/ui/state.cpp b/src/frontend/mame/ui/state.cpp new file mode 100644 index 00000000000..47ebd3fffe1 --- /dev/null +++ b/src/frontend/mame/ui/state.cpp @@ -0,0 +1,423 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + ui/state.cpp + + Menus for saving and loading state + +***************************************************************************/ + +#include "emu.h" +#include "ui/state.h" +#include "emuopts.h" + + +namespace ui { + +/*************************************************************************** + ANONYMOUS NAMESPACE +***************************************************************************/ + +namespace { + +const int MAX_SAVED_STATE_JOYSTICK = 4; + +//------------------------------------------------- +// keyboard_code +//------------------------------------------------- + +input_code keyboard_code(input_item_id id) +{ + // only supported for A-Z|0-9 + assert((id >= ITEM_ID_A && id <= ITEM_ID_Z) || (id >= ITEM_ID_0 && id <= ITEM_ID_9)); + return input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id); +} + + +//------------------------------------------------- +// keyboard_input_item_name +//------------------------------------------------- + +std::string keyboard_input_item_name(input_item_id id) +{ + if (id >= ITEM_ID_A && id <= ITEM_ID_Z) + return std::string(1, char(id - ITEM_ID_A + 'a')); + if (id >= ITEM_ID_0 && id <= ITEM_ID_9) + return std::string(1, char(id - ITEM_ID_0 + '0')); + + // only supported for A-Z/0-9 + throw false; +} + + +//------------------------------------------------- +// code_item_pair +//------------------------------------------------- + +std::pair<std::string, std::string> code_item_pair(const running_machine &machine, input_item_id id) +{ + // identify the input code name (translated appropriately) + input_code code = keyboard_code(id); + std::string code_name = machine.input().code_name(code); + strmakelower(code_name); + + // identify the keyboard item name + std::string input_item_name = keyboard_input_item_name(id); + + // return them + return std::make_pair(code_name, input_item_name); +} + + +}; + +/*************************************************************************** + FILE ENTRY +***************************************************************************/ + +std::string menu_load_save_state_base::s_last_file_selected; + + +//------------------------------------------------- +// file_entry ctor +//------------------------------------------------- + +menu_load_save_state_base::file_entry::file_entry(std::string &&file_name, std::string &&visible_name, const std::chrono::system_clock::time_point &last_modified) + : m_file_name(std::move(file_name)) + , m_visible_name(std::move(visible_name)) + , m_last_modified(last_modified) +{ +} + + +/*************************************************************************** + BASE CLASS FOR LOAD AND SAVE +***************************************************************************/ + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +menu_load_save_state_base::menu_load_save_state_base(mame_ui_manager &mui, render_container &container, const char *header, const char *footer, bool must_exist) + : menu(mui, container) + , m_header(header) + , m_footer(footer) + , m_must_exist(must_exist) + , m_was_paused(false) +{ +} + + +//------------------------------------------------- +// dtor +//------------------------------------------------- + +menu_load_save_state_base::~menu_load_save_state_base() +{ + // resume if appropriate (is the destructor really the right place + // to do this sort of activity?) + if (!m_was_paused) + machine().resume(); +} + + +//------------------------------------------------- +// populate +//------------------------------------------------- + +void menu_load_save_state_base::populate(float &customtop, float &custombottom) +{ + // build the "filename to code" map, if we have not already (if it were not for the + // possibility that the system keyboard can be changed at runtime, I would put this + // into a static) + if (m_filename_to_code_map.empty()) + { + // loop through A-Z/0-9 + for (input_item_id id = ITEM_ID_A; id <= ITEM_ID_Z; id++) + m_filename_to_code_map.emplace(code_item_pair(machine(), id)); + for (input_item_id id = ITEM_ID_0; id <= ITEM_ID_9; id++) + m_filename_to_code_map.emplace(code_item_pair(machine(), id)); + } + + // open the state directory + std::string statedir = state_directory(); + osd::directory::ptr dir = osd::directory::open(statedir); + + // create a separate vector, so we can add sorted entries to the menu + std::vector<const file_entry *> m_entries_vec; + + // populate all file entries + m_file_entries.clear(); + if (dir) + { + const osd::directory::entry *entry; + while ((entry = dir->read()) != nullptr) + { + if (core_filename_ends_with(entry->name, ".sta")) + { + // get the file name of the entry + std::string file_name = core_filename_extract_base(entry->name, true); + + // try translating it + std::string visible_name = get_visible_name(file_name); + + // and proceed + file_entry fileent(std::string(file_name), std::move(visible_name), entry->last_modified); + auto iter = m_file_entries.emplace(std::make_pair(std::move(file_name), std::move(fileent))).first; + m_entries_vec.push_back(&iter->second); + } + } + } + + // sort the vector; put recently modified state files at the top + std::sort( + m_entries_vec.begin(), + m_entries_vec.end(), + [this](const file_entry *a, const file_entry *b) + { + return a->last_modified() > b->last_modified(); + }); + + // add the entries + for (const file_entry *entry : m_entries_vec) + { + // get the time as a local time string + char time_string[128]; + auto last_modified_time_t = std::chrono::system_clock::to_time_t(entry->last_modified()); + std::strftime(time_string, sizeof(time_string), "%c", std::localtime(&last_modified_time_t)); + + // format the text + std::string text = util::string_format("%s: %s", + entry->visible_name(), + time_string); + + // append the menu item + void *itemref = itemref_from_file_entry(*entry); + item_append(std::move(text), std::string(), 0, itemref); + + // is this item selected? + if (entry->file_name() == s_last_file_selected) + set_selection(itemref); + } + + // set up custom render proc + customtop = ui().get_line_height() + 3.0f * UI_BOX_TB_BORDER; + custombottom = ui().get_line_height() + 3.0f * UI_BOX_TB_BORDER; + + // pause if appropriate + m_was_paused = machine().paused(); + if (!m_was_paused) + machine().pause(); +} + + +//------------------------------------------------- +// handle +//------------------------------------------------- + +void menu_load_save_state_base::handle() +{ + // process the menu + const event *event = process(0); + + // process the event + if (event && (event->iptkey == IPT_UI_SELECT)) + { + // user selected one of the entries + const file_entry &entry = file_entry_from_itemref(event->itemref); + slot_selected(std::string(entry.file_name())); + } + else + { + // poll inputs + std::string name = poll_inputs(); + if (!name.empty()) + try_select_slot(std::move(name)); + } +} + + +//------------------------------------------------- +// get_visible_name +//------------------------------------------------- + +std::string menu_load_save_state_base::get_visible_name(const std::string &file_name) +{ + if (file_name.size() == 1) + { + auto iter = m_filename_to_code_map.find(file_name); + if (iter != m_filename_to_code_map.end()) + return iter->second; + } + + // otherwise these are the same + return file_name; +} + + +//------------------------------------------------- +// poll_inputs +//------------------------------------------------- + +std::string menu_load_save_state_base::poll_inputs() +{ + // poll A-Z + for (input_item_id id = ITEM_ID_A; id <= ITEM_ID_Z; id++) + { + if (machine().input().code_pressed_once(keyboard_code(id))) + return keyboard_input_item_name(id); + } + + // poll 0-9 + for (input_item_id id = ITEM_ID_0; id <= ITEM_ID_9; id++) + { + if (machine().input().code_pressed_once(keyboard_code(id))) + return keyboard_input_item_name(id); + } + + // poll joysticks + for (int joy_index = 0; joy_index <= MAX_SAVED_STATE_JOYSTICK; joy_index++) + { + for (input_item_id id = ITEM_ID_BUTTON1; id <= ITEM_ID_BUTTON32; ++id) + { + if (machine().input().code_pressed_once(input_code(DEVICE_CLASS_JOYSTICK, joy_index, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) + return util::string_format("joy%i-%i", joy_index, id - ITEM_ID_BUTTON1 + 1); + } + } + return ""; +} + + +//------------------------------------------------- +// try_select_slot +//------------------------------------------------- + +void menu_load_save_state_base::try_select_slot(std::string &&name) +{ + if (!m_must_exist || is_present(name)) + slot_selected(std::move(name)); +} + + +//------------------------------------------------- +// slot_selected +//------------------------------------------------- + +void menu_load_save_state_base::slot_selected(std::string &&name) +{ + // handle it + process_file(std::string(name)); + + // record the last slot touched + s_last_file_selected = std::move(name); + + // no matter what, pop out + menu::stack_pop(machine()); +} + + +//------------------------------------------------- +// custom_render - perform our special rendering +//------------------------------------------------- + +void menu_load_save_state_base::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + extra_text_render(top, bottom, origx1, origy1, origx2, origy2, + m_header, + m_footer); +} + + +//------------------------------------------------- +// itemref_from_file_entry +//------------------------------------------------- + +void *menu_load_save_state_base::itemref_from_file_entry(const menu_load_save_state_base::file_entry &entry) +{ + return (void *)&entry; +} + + +//------------------------------------------------- +// file_entry_from_itemref +//------------------------------------------------- + +const menu_load_save_state_base::file_entry &menu_load_save_state_base::file_entry_from_itemref(void *itemref) +{ + return *((const file_entry *)itemref); +} + + +//------------------------------------------------- +// is_present +//------------------------------------------------- + +std::string menu_load_save_state_base::state_directory() const +{ + return util::string_format("%s%s%s", + machine().options().state_directory(), + PATH_SEPARATOR, + machine().system().name); +} + + +//------------------------------------------------- +// is_present +//------------------------------------------------- + +bool menu_load_save_state_base::is_present(const std::string &name) const +{ + return m_file_entries.find(name) != m_file_entries.end(); +} + + +/*************************************************************************** + LOAD STATE +***************************************************************************/ + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +menu_load_state::menu_load_state(mame_ui_manager &mui, render_container &container) + : menu_load_save_state_base(mui, container, _("Load State"), _("Select position to load from"), true) +{ +} + + +//------------------------------------------------- +// process_file +//------------------------------------------------- + +void menu_load_state::process_file(std::string &&file_name) +{ + machine().schedule_load(std::move(file_name)); +} + + +/*************************************************************************** + SAVE STATE +***************************************************************************/ + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +menu_save_state::menu_save_state(mame_ui_manager &mui, render_container &container) + : menu_load_save_state_base(mui, container, _("Save State"), _("Select position to save to"), false) +{ +} + + +//------------------------------------------------- +// process_file +//------------------------------------------------- + +void menu_save_state::process_file(std::string &&file_name) +{ + machine().schedule_save(std::move(file_name)); +} + + +} // namespace ui diff --git a/src/frontend/mame/ui/state.h b/src/frontend/mame/ui/state.h new file mode 100644 index 00000000000..5adee6cd712 --- /dev/null +++ b/src/frontend/mame/ui/state.h @@ -0,0 +1,96 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + ui/state.h + + Menus for saving and loading state + +***************************************************************************/ + +#pragma once + +#ifndef MAME_FRONTEND_UI_STATE_H +#define MAME_FRONTEND_UI_STATE_H + +#include "ui/menu.h" + +namespace ui { + +// ======================> menu_load_save_state_base + +class menu_load_save_state_base : public menu +{ +public: + virtual ~menu_load_save_state_base() override; + virtual void populate(float &customtop, float &custombottom) override; + virtual void handle() override; + virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + +protected: + menu_load_save_state_base(mame_ui_manager &mui, render_container &container, const char *header, const char *footer, bool must_exist); + virtual void process_file(std::string &&file_name) = 0; + +private: + class file_entry + { + public: + file_entry() = delete; + file_entry(const file_entry &) = delete; + file_entry(file_entry &&) = default; + file_entry(std::string &&file_name, std::string &&visible_name, const std::chrono::system_clock::time_point &last_modified); + + const std::string &file_name() const { return m_file_name; } + const std::string &visible_name() const { return m_visible_name; } + const std::chrono::system_clock::time_point &last_modified() const { return m_last_modified; } + + private: + std::string m_file_name; // filename for the state itself + std::string m_visible_name; // how it appears in the dialog + std::chrono::system_clock::time_point m_last_modified; + }; + + static std::string s_last_file_selected; + + std::unordered_map<std::string, file_entry> m_file_entries; + std::unordered_map<std::string, std::string> m_filename_to_code_map; + const char * m_header; + const char * m_footer; + bool m_must_exist; + bool m_was_paused; + + static void *itemref_from_file_entry(const file_entry &entry); + static const file_entry &file_entry_from_itemref(void *itemref); + void try_select_slot(std::string &&name); + void slot_selected(std::string &&name); + std::string state_directory() const; + bool is_present(const std::string &name) const; + std::string poll_inputs(); + std::string get_visible_name(const std::string &file_name); +}; + +// ======================> menu_load_state + +class menu_load_state : public menu_load_save_state_base +{ +public: + menu_load_state(mame_ui_manager &mui, render_container &container); + +protected: + virtual void process_file(std::string &&file_name) override; +}; + +// ======================> menu_save_state + +class menu_save_state : public menu_load_save_state_base +{ +public: + menu_save_state(mame_ui_manager &mui, render_container &container); + +protected: + virtual void process_file(std::string &&file_name) override; +}; + +}; + +#endif // MAME_FRONTEND_UI_STATE_H diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index 4b570491d0f..7ea8b4c71bb 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -27,6 +27,7 @@ #include "ui/mainmenu.h" #include "ui/filemngr.h" #include "ui/sliders.h" +#include "ui/state.h" #include "ui/viewgfx.h" #include "imagedev/cassette.h" @@ -42,8 +43,6 @@ enum LOADSAVE_SAVE }; -#define MAX_SAVED_STATE_JOYSTICK 4 - /*************************************************************************** LOCAL VARIABLES @@ -183,8 +182,7 @@ mame_ui_manager::mame_ui_manager(running_machine &machine) , m_show_profiler(false) , m_popup_text_end(0) , m_mouse_arrow_texture(nullptr) - , m_mouse_show(false) - , m_load_save_hold(false) {} + , m_mouse_show(false) {} mame_ui_manager::~mame_ui_manager() { @@ -988,10 +986,8 @@ void mame_ui_manager::draw_profiler(render_container &container) void mame_ui_manager::start_save_state() { - machine().pause(); - m_load_save_hold = true; - using namespace std::placeholders; - set_handler(ui_callback_type::GENERAL, std::bind(&mame_ui_manager::handler_load_save, this, _1, uint32_t(LOADSAVE_SAVE))); + show_menu(); + ui::menu::stack_push<ui::menu_save_state>(*this, machine().render().ui_container()); } @@ -1001,10 +997,8 @@ void mame_ui_manager::start_save_state() void mame_ui_manager::start_load_state() { - machine().pause(); - m_load_save_hold = true; - using namespace std::placeholders; - set_handler(ui_callback_type::GENERAL, std::bind(&mame_ui_manager::handler_load_save, this, _1, uint32_t(LOADSAVE_LOAD))); + show_menu(); + ui::menu::stack_push<ui::menu_load_state>(*this, machine().render().ui_container()); } @@ -1270,111 +1264,6 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) //------------------------------------------------- -// handler_load_save - leads the user through -// specifying a game to save or load -//------------------------------------------------- - -uint32_t mame_ui_manager::handler_load_save(render_container &container, uint32_t state) -{ - std::string filename; - char file = 0; - - // if we're not in the middle of anything, skip - if (state == LOADSAVE_NONE) - return 0; - - // okay, we're waiting for a key to select a slot; display a message - if (state == LOADSAVE_SAVE) - draw_message_window(container, _("Select position to save to")); - else - draw_message_window(container, _("Select position to load from")); - - // if load/save state sequence is still being pressed, do not read the filename yet - if (m_load_save_hold) { - bool seq_in_progress = false; - const input_seq &load_save_seq = state == LOADSAVE_SAVE ? - machine().ioport().type_seq(IPT_UI_SAVE_STATE) : - machine().ioport().type_seq(IPT_UI_LOAD_STATE); - - for (int i = 0; i < load_save_seq.length(); i++) - if (machine().input().code_pressed_once(load_save_seq[i])) - seq_in_progress = true; - - if (seq_in_progress) - return state; - else - m_load_save_hold = false; - } - - // check for cancel key - if (machine().ui_input().pressed(IPT_UI_CANCEL)) - { - // display a popup indicating things were cancelled - if (state == LOADSAVE_SAVE) - machine().popmessage(_("Save cancelled")); - else - machine().popmessage(_("Load cancelled")); - - // reset the state - machine().resume(); - return UI_HANDLER_CANCEL; - } - - // check for A-Z or 0-9 - for (input_item_id id = ITEM_ID_A; id <= ITEM_ID_Z; ++id) - if (machine().input().code_pressed_once(input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) - file = id - ITEM_ID_A + 'a'; - if (file == 0) - for (input_item_id id = ITEM_ID_0; id <= ITEM_ID_9; ++id) - if (machine().input().code_pressed_once(input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) - file = id - ITEM_ID_0 + '0'; - if (file == 0) - for (input_item_id id = ITEM_ID_0_PAD; id <= ITEM_ID_9_PAD; ++id) - if (machine().input().code_pressed_once(input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) - file = id - ITEM_ID_0_PAD + '0'; - if (file == 0) - { - bool found = false; - - for (int joy_index = 0; joy_index <= MAX_SAVED_STATE_JOYSTICK; joy_index++) - for (input_item_id id = ITEM_ID_BUTTON1; id <= ITEM_ID_BUTTON32; ++id) - if (machine().input().code_pressed_once(input_code(DEVICE_CLASS_JOYSTICK, joy_index, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) - { - filename = util::string_format("joy%i-%i", joy_index, id - ITEM_ID_BUTTON1 + 1); - found = true; - break; - } - - if (!found) - return state; - } - else - { - filename = util::string_format("%c", file); - } - - // display a popup indicating that the save will proceed - if (state == LOADSAVE_SAVE) - { - machine().popmessage(_("Save to position %s"), filename); - machine().schedule_save(std::move(filename)); - } - else - { - machine().popmessage(_("Load from position %s"), filename); - machine().schedule_load(std::move(filename)); - } - - // avoid handling the name of the save state slot as a seperate input - machine().ui_input().mark_all_as_pressed(); - - // remove the pause and reset the state - machine().resume(); - return UI_HANDLER_CANCEL; -} - - -//------------------------------------------------- // request_quit //------------------------------------------------- diff --git a/src/frontend/mame/ui/ui.h b/src/frontend/mame/ui/ui.h index 8364fa69ec2..ce83bb692cc 100644 --- a/src/frontend/mame/ui/ui.h +++ b/src/frontend/mame/ui/ui.h @@ -245,7 +245,6 @@ private: std::unique_ptr<uint8_t[]> m_non_char_keys_down; render_texture * m_mouse_arrow_texture; bool m_mouse_show; - bool m_load_save_hold; ui_options m_ui_options; std::unique_ptr<ui::machine_info> m_machine_info; |