summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
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
commit82974981a6c36e041e798ce475aa4539866e6f0e (patch)
treeef6eb54a2227c2993a40ab3e575638b6d244bf24
parent4d50a021114654c2aad0ed92e3b57a664cb9f22d (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.lua2
-rw-r--r--src/frontend/mame/ui/state.cpp330
-rw-r--r--src/frontend/mame/ui/state.h90
-rw-r--r--src/frontend/mame/ui/ui.cpp121
-rw-r--r--src/frontend/mame/ui/ui.h1
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;