diff options
author | npwoods <npwoods@alumni.cmu.edu> | 2017-06-01 06:56:36 -0400 |
---|---|---|
committer | Olivier Galibert <galibert@pobox.com> | 2017-06-01 12:56:36 +0200 |
commit | 82974981a6c36e041e798ce475aa4539866e6f0e (patch) | |
tree | ef6eb54a2227c2993a40ab3e575638b6d244bf24 | |
parent | 4d50a021114654c2aad0ed92e3b57a664cb9f22d (diff) |
state: Turns the current state save feature to a menu [Nathan Woods]
* 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
-rw-r--r-- | scripts/src/mame/frontend.lua | 2 | ||||
-rw-r--r-- | src/frontend/mame/ui/state.cpp | 330 | ||||
-rw-r--r-- | src/frontend/mame/ui/state.h | 90 | ||||
-rw-r--r-- | src/frontend/mame/ui/ui.cpp | 121 | ||||
-rw-r--r-- | src/frontend/mame/ui/ui.h | 1 |
5 files changed, 428 insertions, 116 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..59734909d53 --- /dev/null +++ b/src/frontend/mame/ui/state.cpp @@ -0,0 +1,330 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + ui/state.cpp + + Menus for saving and loading state + +***************************************************************************/ + +#include "emu.h" +#include "machine.h" +#include "emuopts.h" +#include "ui/state.h" + + +/*************************************************************************** + ANONYMOUS NAMESPACE +***************************************************************************/ + +namespace { + +//------------------------------------------------- +// is_valid_state_char - is the specified character +// a valid state filename character? +//------------------------------------------------- + +bool is_valid_state_char(char32_t ch) +{ + return uchar_is_printable(ch) && osd_is_valid_filename_char(ch); +} + + +//------------------------------------------------- +// get_entry_char +//------------------------------------------------- + +char32_t get_entry_char(const osd::directory::entry &entry) +{ + char32_t result = 0; + + // first, is this a file that ends with *.sta? + if (entry.type == osd::directory::entry::entry_type::FILE + && core_filename_ends_with(entry.name, ".sta")) + { + std::string basename = core_filename_extract_base(entry.name); + + + char32_t ch; + if (uchar_from_utf8(&ch, basename.c_str(), basename.length() == basename.length()) && is_valid_state_char(ch)) + result = ch; + } + return result; +} + + +}; + +namespace ui { + +/*************************************************************************** + FILE ENTRY +***************************************************************************/ + +char32_t menu_load_save_state_base::s_last_file_selected; + +//------------------------------------------------- +// file_entry ctor +//------------------------------------------------- + +menu_load_save_state_base::file_entry::file_entry(char32_t entry_char, const std::chrono::system_clock::time_point &last_modified) + : m_entry_char(entry_char) + , 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) +{ + // 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) + { + char32_t entry_char = get_entry_char(*entry); + if (entry_char) + { + if (core_filename_ends_with(entry->name, ".sta")) + { + file_entry fileent(entry_char, entry->last_modified); + auto iter = m_file_entries.emplace(std::make_pair(entry_char, 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", + utf8_from_uchar(entry->entry_char()), + 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->entry_char() == 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 != nullptr) && (event->iptkey == IPT_UI_SELECT)) + { + // user selected one of the entries + const file_entry &entry = file_entry_from_itemref(event->itemref); + slot_selected(entry.entry_char()); + } + else if ((event != nullptr) && (event->iptkey == IPT_SPECIAL) + && is_valid_state_char(event->unichar) + && (!m_must_exist || is_present(event->unichar))) + { + // user pressed a shortcut key + slot_selected(event->unichar); + } +} + + +//------------------------------------------------- +// slot_selected +//------------------------------------------------- + +void menu_load_save_state_base::slot_selected(char32_t entry_char) +{ + // handle it + process_file(utf8_from_uchar(entry_char)); + + // record the last slot touched + s_last_file_selected = entry_char; + + // 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(char32_t entry_char) const +{ + return m_file_entries.find(entry_char) != 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..171ec960b0d --- /dev/null +++ b/src/frontend/mame/ui/state.h @@ -0,0 +1,90 @@ +// 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(char32_t entry_char, const std::chrono::system_clock::time_point &last_modified); + + char32_t entry_char() const { return m_entry_char; } + const std::chrono::system_clock::time_point &last_modified() const { return m_last_modified; } + + private: + char32_t m_entry_char; + std::chrono::system_clock::time_point m_last_modified; + }; + + static char32_t s_last_file_selected; + + std::unordered_map<char32_t, file_entry> m_file_entries; + 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 slot_selected(char32_t entry_char); + std::string state_directory() const; + bool is_present(char32_t entry_char) const; +}; + +// ======================> 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..5c942c803ba 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" @@ -183,8 +184,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 +988,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 +999,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 +1266,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; |