Controller Mapping: System Profiles & Unmap Button (#269)

* add unmap button
* separate controller profiles for dc & arcade games
* loads current system mapping on menu exit
* simplify, use default filename for dc controls
* create blank mapping file for sdl controllers if not found
This commit is contained in:
Enrique Santos 2021-06-27 13:49:47 +03:00 committed by GitHub
parent b3ad0a62c1
commit 46e3258629
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 203 additions and 5 deletions

View File

@ -22,9 +22,11 @@
#include "oslib/oslib.h"
#include "rend/gui.h"
#include "emulator.h"
#include "stdclass.h"
#include <algorithm>
#include <climits>
#include <fstream>
#define MAPLE_PORT_CFG_PREFIX "maple_"
@ -320,6 +322,84 @@ std::string GamepadDevice::make_mapping_filename(bool instance)
return mapping_file;
}
void GamepadDevice::verify_or_create_system_mappings()
{
std::string dc_name = make_mapping_filename(false, 0);
std::string arcade_name = make_mapping_filename(false, 2);
std::string dc_path = get_readonly_config_path(std::string("mappings/") + dc_name);
std::string arcade_path = get_readonly_config_path(std::string("mappings/") + arcade_name);
if (!file_exists(arcade_path))
{
save_mapping(2);
input_mapper->ClearMappings();
}
if (!file_exists(dc_path))
{
save_mapping(0);
input_mapper->ClearMappings();
}
find_mapping(DC_PLATFORM_DREAMCAST);
}
void GamepadDevice::load_system_mappings(int system)
{
for (int i = 0; i < GetGamepadCount(); i++)
{
std::shared_ptr<GamepadDevice> gamepad = GetGamepad(i);
gamepad->find_mapping(system);
}
}
std::string GamepadDevice::make_mapping_filename(bool instance, int system)
{
std::string mapping_file = api_name() + "_" + name();
if (instance)
mapping_file += "-" + _unique_id;
if (system != 0)
mapping_file += "_arcade";
std::replace(mapping_file.begin(), mapping_file.end(), '/', '-');
std::replace(mapping_file.begin(), mapping_file.end(), '\\', '-');
std::replace(mapping_file.begin(), mapping_file.end(), ':', '-');
std::replace(mapping_file.begin(), mapping_file.end(), '?', '-');
std::replace(mapping_file.begin(), mapping_file.end(), '*', '-');
std::replace(mapping_file.begin(), mapping_file.end(), '|', '-');
std::replace(mapping_file.begin(), mapping_file.end(), '"', '-');
std::replace(mapping_file.begin(), mapping_file.end(), '<', '-');
std::replace(mapping_file.begin(), mapping_file.end(), '>', '-');
mapping_file += ".cfg";
return mapping_file;
}
bool GamepadDevice::find_mapping(int system)
{
std::string mapping_file;
mapping_file = make_mapping_filename(false, system);
// fall back on default flycast mapping filename if system profile not found
std::string system_mapping_path = get_readonly_config_path(std::string("mappings/") + mapping_file);
if (!file_exists(system_mapping_path))
mapping_file = make_mapping_filename(false);
input_mapper = InputMapping::LoadMapping(mapping_file.c_str());
// fallback to default mapping filename for sdl inputs
if (!input_mapper && mapping_file.find("SDL") != std::string::npos)
{
mapping_file = make_mapping_filename(false);
std::string mapping_path = get_readonly_config_path(std::string("mappings/") + mapping_file);
// create default mapping filename if none exists
if (!file_exists(mapping_path))
std::ofstream file{ mapping_path.c_str() };
input_mapper = InputMapping::LoadMapping(mapping_file.c_str());
}
return !!input_mapper;
}
bool GamepadDevice::find_mapping(const char *custom_mapping /* = nullptr */)
{
std::string mapping_file;
@ -365,6 +445,15 @@ void GamepadDevice::save_mapping()
InputMapping::SaveMapping(filename.c_str(), input_mapper);
}
void GamepadDevice::save_mapping(int system)
{
if (!input_mapper)
return;
std::string filename = make_mapping_filename(false, system);
input_mapper->set_dirty();
InputMapping::SaveMapping(filename.c_str(), input_mapper);
}
void UpdateVibration(u32 port, float power, float inclination, u32 duration_ms)
{
int i = GamepadDevice::GetGamepadCount() - 1;

View File

@ -48,6 +48,10 @@ public:
}
std::shared_ptr<InputMapping> get_input_mapping() { return input_mapper; }
void save_mapping();
void save_mapping(int system);
void verify_or_create_system_mappings();
virtual const char *get_button_name(u32 code) { return nullptr; }
virtual const char *get_axis_name(u32 code) { return nullptr; }
bool remappable() { return _remappable && input_mapper; }
@ -65,6 +69,8 @@ public:
static std::shared_ptr<GamepadDevice> GetGamepad(int index);
static void SaveMaplePorts();
static void load_system_mappings(int system = settings.platform.system);
bool find_mapping(int system);
protected:
GamepadDevice(int maple_port, const char *api_name, bool remappable = true)
: _api_name(api_name), _maple_port(maple_port), _input_detected(nullptr), _remappable(remappable)
@ -86,6 +92,7 @@ private:
int get_axis_min_value(u32 axis);
unsigned int get_axis_range(u32 axis);
std::string make_mapping_filename(bool instance = false);
std::string make_mapping_filename(bool instance, int system);
std::string _api_name;
int _maple_port;

View File

@ -97,6 +97,21 @@ axis_list[] =
std::map<std::string, std::shared_ptr<InputMapping>> InputMapping::loaded_mappings;
void InputMapping::clear_button(u32 port, DreamcastKey id, u32 code)
{
if (id != EMU_BTN_NONE)
{
while (true)
{
u32 code = get_button_code(port, id);
if (code == (u32)-1)
break;
buttons[port][code] = EMU_BTN_NONE;
}
dirty = true;
}
}
void InputMapping::set_button(u32 port, DreamcastKey id, u32 code)
{
if (id != EMU_BTN_NONE)
@ -113,6 +128,21 @@ void InputMapping::set_button(u32 port, DreamcastKey id, u32 code)
}
}
void InputMapping::clear_axis(u32 port, DreamcastKey id, u32 code)
{
if (id != EMU_AXIS_NONE)
{
while (true)
{
u32 code = get_axis_code(port, id);
if (code == (u32)-1)
break;
axes[port][code] = EMU_AXIS_NONE;
}
dirty = true;
}
}
void InputMapping::set_axis(u32 port, DreamcastKey id, u32 code, bool is_inverted)
{
if (id != EMU_AXIS_NONE)
@ -200,6 +230,11 @@ u32 InputMapping::get_axis_code(u32 port, DreamcastKey key)
return -1;
}
void InputMapping::ClearMappings()
{
loaded_mappings.clear();
}
std::shared_ptr<InputMapping> InputMapping::LoadMapping(const char *name)
{
auto it = loaded_mappings.find(name);
@ -218,6 +253,11 @@ std::shared_ptr<InputMapping> InputMapping::LoadMapping(const char *name)
return mapping;
}
void InputMapping::set_dirty()
{
dirty = true;
}
bool InputMapping::save(const char *name)
{
if (!dirty)

View File

@ -50,6 +50,7 @@ public:
else
return EMU_BTN_NONE;
}
void clear_button(u32 port, DreamcastKey id, u32 code);
void set_button(u32 port, DreamcastKey id, u32 code);
void set_button(DreamcastKey id, u32 code) { set_button(0, id, code); }
u32 get_button_code(u32 port, DreamcastKey key);
@ -71,17 +72,21 @@ public:
return false;
}
u32 get_axis_code(u32 port, DreamcastKey key);
void clear_axis(u32 port, DreamcastKey id, u32 code);
void set_axis(u32 port, DreamcastKey id, u32 code, bool inverted);
void set_axis(DreamcastKey id, u32 code, bool inverted) { set_axis(0, id, code, inverted); }
void load(FILE* fp);
bool save(const char *name);
void set_dirty();
bool is_dirty() const { return dirty; }
static std::shared_ptr<InputMapping> LoadMapping(const char *name);
static void SaveMapping(const char *name, const std::shared_ptr<InputMapping>& mapping);
void ClearMappings();
protected:
bool dirty = false;

View File

@ -493,6 +493,8 @@ static void dc_start_game(const char *path)
config::Settings::instance().reset();
dc_reset(true);
config::Settings::instance().load(false);
GamepadDevice::load_system_mappings();
if (settings.platform.system == DC_PLATFORM_DREAMCAST)
{

View File

@ -63,6 +63,7 @@ static std::string osd_message;
static double osd_message_end;
static std::mutex osd_message_mutex;
static int map_system = 0;
static void display_vmus();
static void reset_vmus();
static void term_vmus();
@ -390,6 +391,7 @@ void gui_open_settings()
else if (gui_state == GuiState::Commands)
{
gui_state = GuiState::Closed;
GamepadDevice::load_system_mappings();
dc_resume();
}
}
@ -459,6 +461,7 @@ static void gui_display_commands()
ImGui::NextColumn();
if (ImGui::Button("Resume", ImVec2(150 * scaling, 50 * scaling)))
{
GamepadDevice::load_system_mappings();
gui_state = GuiState::Closed;
}
@ -682,13 +685,14 @@ static void controller_mapping_popup(const std::shared_ptr<GamepadDevice>& gamep
const ImGuiStyle& style = ImGui::GetStyle();
const float width = (ImGui::GetIO().DisplaySize.x - insetLeft - insetRight - style.ItemSpacing.x) / 2 - style.WindowBorderSize - style.WindowPadding.x;
const float col_width = (width - style.GrabMinSize - style.ItemSpacing.x
- (ImGui::CalcTextSize("Map").x + style.FramePadding.x * 2.0f + style.ItemSpacing.x)) / 2;
- (ImGui::CalcTextSize("Map").x + style.FramePadding.x * 2.0f + style.ItemSpacing.x)
- (ImGui::CalcTextSize("Unmap").x + style.FramePadding.x * 2.0f + style.ItemSpacing.x)) / 2;
std::shared_ptr<InputMapping> input_mapping = gamepad->get_input_mapping();
if (input_mapping == NULL || ImGui::Button("Done", ImVec2(100 * scaling, 30 * scaling)))
{
ImGui::CloseCurrentPopup();
gamepad->save_mapping();
gamepad->save_mapping(map_system);
}
ImGui::SetItemDefaultFocus();
@ -711,9 +715,45 @@ static void controller_mapping_popup(const std::shared_ptr<GamepadDevice>& gamep
}
ImGui::PopItemWidth();
}
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Arcade button names").x
- style.FramePadding.x * 3.0f - style.ItemSpacing.x);
ImGui::Checkbox("Arcade button names", &arcade_button_mode);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Dreamcast Controls").x
- ImGui::GetStyle().FramePadding.x * 3.0f - ImGui::GetStyle().ItemSpacing.x * 3.0f);
ImGui::AlignTextToFramePadding();
static ImGuiComboFlags flags = 0;
const char* items[] = { "Dreamcast Controls", "Arcade Controls" };
static int item_current_map_idx = 0;
static int last_item_current_map_idx = 2;
// Here our selection data is an index.
const char* combo_label = items[item_current_map_idx]; // Label to preview before opening the combo (technically it could be anything)
ImGui::PushItemWidth(ImGui::CalcTextSize("Dreamcast Controls").x + ImGui::GetStyle().ItemSpacing.x * 2.0f * 3);
ImGui::Combo("", &item_current_map_idx, items, IM_ARRAYSIZE(items));
if (item_current_map_idx != last_item_current_map_idx)
{
gamepad->save_mapping(map_system);
}
if (item_current_map_idx == 0)
{
arcade_button_mode = false;
map_system = DC_PLATFORM_DREAMCAST;
}
else if (item_current_map_idx == 1)
{
arcade_button_mode = true;
map_system = DC_PLATFORM_NAOMI;
}
if (item_current_map_idx != last_item_current_map_idx)
{
gamepad->find_mapping(map_system);
input_mapping = gamepad->get_input_mapping();
last_item_current_map_idx = item_current_map_idx;
}
char key_id[32];
ImGui::BeginGroup();
@ -723,6 +763,8 @@ static void controller_mapping_popup(const std::shared_ptr<GamepadDevice>& gamep
ImGui::Columns(3, "bindings", false);
ImGui::SetColumnWidth(0, col_width);
ImGui::SetColumnWidth(1, col_width);
gamepad->find_mapping(map_system);
for (u32 j = 0; j < ARRAY_SIZE(button_keys); j++)
{
sprintf(key_id, "key_id%d", j);
@ -758,6 +800,12 @@ static void controller_mapping_popup(const std::shared_ptr<GamepadDevice>& gamep
});
}
detect_input_popup(j, false);
ImGui::SameLine();
if (ImGui::Button("Unmap"))
{
input_mapping = gamepad->get_input_mapping();
input_mapping->clear_button(gamepad_port, button_keys[j], j);
}
ImGui::NextColumn();
ImGui::PopID();
}
@ -812,6 +860,12 @@ static void controller_mapping_popup(const std::shared_ptr<GamepadDevice>& gamep
});
}
detect_input_popup(j, true);
ImGui::SameLine();
if (ImGui::Button("Unmap"))
{
input_mapping = gamepad->get_input_mapping();
input_mapping->clear_axis(gamepad_port, axis_keys[j], j);
}
ImGui::NextColumn();
ImGui::PopID();
}
@ -1130,6 +1184,7 @@ static void gui_display_settings()
if (gamepad->remappable() && ImGui::Button("Map"))
{
gamepad_port = 0;
gamepad->verify_or_create_system_mappings();
ImGui::OpenPopup("Controller Mapping");
}