summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
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
commit1b28df0b39225c3017e7bc843c14b58f548162ac (patch)
tree7ceec13527959f1631fd2d6d9d1dc9eb3f8f700c
parent69828a8ff9dfe5e56ac00accfddc991f5dce7d61 (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.lua2
-rw-r--r--src/frontend/mame/ui/state.cpp423
-rw-r--r--src/frontend/mame/ui/state.h96
-rw-r--r--src/frontend/mame/ui/ui.cpp123
-rw-r--r--src/frontend/mame/ui/ui.h1
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;