diff --git a/assets/icons/Keyboard.svg b/assets/icons/Keyboard.svg
new file mode 100644
index 0000000..5f68e78
--- /dev/null
+++ b/assets/icons/Keyboard.svg
@@ -0,0 +1,16 @@
+
diff --git a/include/recomp_input.h b/include/recomp_input.h
index a1f130f..7ceb044 100644
--- a/include/recomp_input.h
+++ b/include/recomp_input.h
@@ -13,6 +13,9 @@
#include "json/json.hpp"
+#include "SDL.h"
+#include "chrono"
+
namespace recomp {
// x-macros to build input enums and arrays.
// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name.
@@ -250,16 +253,33 @@ namespace recompinput {
BindingState& get_binding_state();
int get_num_players();
- bool get_player_is_assigned(int player_index);
-
+
+ struct AssignedPlayer {
+ SDL_GameController* controller = nullptr;
+ bool is_assigned = false;
+ bool keyboard_enabled = false;
+ std::chrono::high_resolution_clock::duration last_button_press_timestamp = std::chrono::high_resolution_clock::duration::zero();
+
+ AssignedPlayer() : controller(nullptr), is_assigned(false), keyboard_enabled(false), last_button_press_timestamp(std::chrono::high_resolution_clock::duration::zero()) {};
+ };
+
+ constexpr size_t temp_max_players = 4;
+
struct PlayerAssignmentState {
bool is_assigning = false;
int player_index = 0;
+ std::array temp_assigned_players;
+
+ PlayerAssignmentState() : is_assigning(false), player_index(0), temp_assigned_players{} {};
};
+ bool get_player_is_assigned(int player_index);
void start_player_assignment(void);
void stop_player_assignment(void);
+ void stop_player_assignment_and_close_modal(void);
+ void commit_player_assignment(void);
bool is_player_assignment_active();
+ std::chrono::steady_clock::duration get_player_time_since_last_button_press(int player_index);
bool does_player_have_controller(int player_index);
}
diff --git a/src/game/input.cpp b/src/game/input.cpp
index 2672a65..108ece5 100644
--- a/src/game/input.cpp
+++ b/src/game/input.cpp
@@ -24,12 +24,6 @@ struct ControllerState {
};
};
-struct AssignedPlayer {
- SDL_GameController* controller = nullptr;
- bool is_assigned = false;
- bool keyboard_enabled = false;
-};
-
static struct {
const Uint8* keys = nullptr;
SDL_Keymod keymod = SDL_Keymod::KMOD_NONE;
@@ -38,7 +32,7 @@ static struct {
std::mutex controllers_mutex;
std::vector detected_controllers{};
std::vector detected_controller_options{};
- std::array assigned_controllers{}; // Only used when Multiplayer is enabled.
+ std::array assigned_controllers{}; // Only used when Multiplayer is enabled.
std::unordered_map controller_states;
bool single_controller = false;
@@ -60,7 +54,7 @@ std::atomic scanning_device = recomp::InputDevice::COUNT;
std::atomic scanned_input;
static recompinput::BindingState binding_state;
-static recompinput::PlayerAssignmentState player_assignment_state;
+static recompinput::PlayerAssignmentState player_assignment_state{};
void recompinput::start_scanning_for_binding(int player_index, recomp::GameInput game_input, int binding_index) {
binding_state.is_scanning = true;
@@ -91,24 +85,36 @@ bool recompinput::get_player_is_assigned(int player_index) {
return false;
}
- return InputState.assigned_controllers[player_index].is_assigned;
+ return player_assignment_state.temp_assigned_players[player_index].is_assigned;
}
void recompinput::start_player_assignment() {
player_assignment_state.is_assigning = true;
player_assignment_state.player_index = 0;
- for (auto& player : InputState.assigned_controllers) {
- player.controller = nullptr;
- player.is_assigned = false;
- player.keyboard_enabled = false;
+ for (auto& player : player_assignment_state.temp_assigned_players) {
+ player = AssignedPlayer{};
}
}
+static bool queue_close_player_assignment_modal = false;
+
void recompinput::stop_player_assignment() {
player_assignment_state.is_assigning = false;
player_assignment_state.player_index = -1;
- recompui::assign_players_modal->close();
+}
+
+void recompinput::stop_player_assignment_and_close_modal() {
+ recompinput::stop_player_assignment();
+ queue_close_player_assignment_modal = true;
+}
+
+void recompinput::commit_player_assignment() {
+ recompinput::stop_player_assignment_and_close_modal();
+
+ for (int i = 0; i < recompinput::get_num_players(); i++) {
+ InputState.assigned_controllers[i] = player_assignment_state.temp_assigned_players[i];
+ }
}
bool recompinput::is_player_assignment_active() {
@@ -119,10 +125,22 @@ bool recompinput::does_player_have_controller(int player_index) {
if (player_index < 0 || player_index >= recompinput::get_num_players()) {
return false;
}
- return InputState.assigned_controllers[player_index].controller != nullptr;
+ return player_assignment_state.temp_assigned_players[player_index].controller != nullptr;
+}
+
+std::chrono::steady_clock::duration recompinput::get_player_time_since_last_button_press(int player_index) {
+ if (player_index < 0 || player_index >= recompinput::get_num_players()) {
+ return std::chrono::steady_clock::duration::zero();
+ }
+ return ultramodern::time_since_start() - player_assignment_state.temp_assigned_players[player_index].last_button_press_timestamp;
}
void process_player_assignment(SDL_Event* event) {
+ if (queue_close_player_assignment_modal) {
+ recompui::assign_players_modal->close();
+ queue_close_player_assignment_modal = false;
+ }
+
if (!player_assignment_state.is_assigning) {
return;
}
@@ -139,11 +157,18 @@ void process_player_assignment(SDL_Event* event) {
recompinput::stop_player_assignment();
return;
case SDL_Scancode::SDL_SCANCODE_SPACE:
- InputState.assigned_controllers[player_assignment_state.player_index].is_assigned = true;
- InputState.assigned_controllers[player_assignment_state.player_index].keyboard_enabled = true;
+ player_assignment_state.temp_assigned_players[player_assignment_state.player_index].is_assigned = true;
+ player_assignment_state.temp_assigned_players[player_assignment_state.player_index].keyboard_enabled = true;
player_assignment_state.player_index++;
printf("Assigned keyboard to player %d\n", player_assignment_state.player_index - 1);
break;
+ default:
+ for (int i = 0; i < player_assignment_state.player_index; i++) {
+ if (player_assignment_state.temp_assigned_players[i].keyboard_enabled && player_assignment_state.temp_assigned_players[i].controller == nullptr) {
+ player_assignment_state.temp_assigned_players[i].last_button_press_timestamp = ultramodern::time_since_start();
+ }
+ }
+ break;
}
break;
}
@@ -154,14 +179,18 @@ void process_player_assignment(SDL_Event* event) {
bool can_be_mapped = true;
for (int i = 0; i < player_assignment_state.player_index; i++) {
- if (InputState.assigned_controllers[i].controller == controller_state.controller) {
+ if (player_assignment_state.temp_assigned_players[i].controller == controller_state.controller) {
can_be_mapped = false;
+ player_assignment_state.temp_assigned_players[i].last_button_press_timestamp = ultramodern::time_since_start();
break;
}
}
+
if (can_be_mapped) {
- InputState.assigned_controllers[player_assignment_state.player_index].is_assigned = true;
- InputState.assigned_controllers[player_assignment_state.player_index].controller = controller_state.controller;
+ recompinput::AssignedPlayer& assigned_player = player_assignment_state.temp_assigned_players[player_assignment_state.player_index];
+ assigned_player.is_assigned = true;
+ assigned_player.controller = controller_state.controller;
+ assigned_player.last_button_press_timestamp = ultramodern::time_since_start();
player_assignment_state.player_index++;
printf("Assigned controller %d to player %d\n", joystick_id, player_assignment_state.player_index - 1);
}
diff --git a/src/ui/ui_assign_players_modal.cpp b/src/ui/ui_assign_players_modal.cpp
index ccb6336..cee45d7 100644
--- a/src/ui/ui_assign_players_modal.cpp
+++ b/src/ui/ui_assign_players_modal.cpp
@@ -1,11 +1,84 @@
#include "ui_assign_players_modal.h"
#include "elements/ui_label.h"
+#include "elements/ui_container.h"
#include "recomp_ui.h"
namespace recompui {
recompui::ContextId assign_players_modal_context;
+static constexpr float assign_player_card_size = 128.0f;
+static constexpr float assign_player_card_icon_size = 64.0f;
+AssignPlayerCard::AssignPlayerCard(Element *parent) : Element(parent, 0, "div", false) {
+ set_display(Display::Flex);
+ set_flex_direction(FlexDirection::Column);
+ set_align_items(AlignItems::Center);
+ set_justify_content(JustifyContent::Center);
+ set_width(assign_player_card_size);
+ set_height(assign_player_card_size);
+ set_border_color(theme::color::BorderSoft);
+ set_border_width(theme::border::width, Unit::Dp);
+ set_border_radius(theme::border::radius_sm, Unit::Dp);
+ set_background_color(theme::color::Transparent);
+
+ recompui::ContextId context = get_current_context();
+ icon = context.create_element