mirror of
https://github.com/BanjoRecomp/BanjoRecomp
synced 2026-05-23 22:45:12 -04:00
button press anims & temp reassignment state when opening assignment modal
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="9" width="26" height="14" rx="2" stroke="white" stroke-width="4" stroke-miterlimit="2.85545" stroke-linejoin="round"/>
|
||||
<line x1="5" y1="14" x2="27" y2="14" stroke="white" stroke-width="2"/>
|
||||
<line x1="5" y1="18" x2="27" y2="18" stroke="white" stroke-width="2"/>
|
||||
<line x1="14" y1="15" x2="14" y2="19" stroke="white" stroke-width="2"/>
|
||||
<line x1="24" y1="11" x2="24" y2="13" stroke="white" stroke-width="2"/>
|
||||
<line x1="20" y1="11" x2="20" y2="13" stroke="white" stroke-width="2"/>
|
||||
<line x1="16" y1="11" x2="16" y2="13" stroke="white" stroke-width="2"/>
|
||||
<line x1="18" y1="15" x2="18" y2="19" stroke="white" stroke-width="2"/>
|
||||
<line x1="24" y1="19" x2="24" y2="21" stroke="white" stroke-width="2"/>
|
||||
<line x1="22" y1="15" x2="22" y2="17" stroke="white" stroke-width="2"/>
|
||||
<line x1="8" y1="11" x2="8" y2="13" stroke="white" stroke-width="2"/>
|
||||
<line x1="12" y1="11" x2="12" y2="13" stroke="white" stroke-width="2"/>
|
||||
<line x1="10" y1="15" x2="10" y2="17" stroke="white" stroke-width="2"/>
|
||||
<line x1="8" y1="19" x2="8" y2="21" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
+22
-2
@@ -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<AssignedPlayer, temp_max_players> 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);
|
||||
}
|
||||
|
||||
+49
-20
@@ -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<SDL_GameController*> detected_controllers{};
|
||||
std::vector<recomp::ControllerOption> detected_controller_options{};
|
||||
std::array<AssignedPlayer, 4> assigned_controllers{}; // Only used when Multiplayer is enabled.
|
||||
std::array<recompinput::AssignedPlayer, recompinput::temp_max_players> assigned_controllers{}; // Only used when Multiplayer is enabled.
|
||||
std::unordered_map<SDL_JoystickID, ControllerState> controller_states;
|
||||
bool single_controller = false;
|
||||
|
||||
@@ -60,7 +54,7 @@ std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
|
||||
std::atomic<recomp::InputField> 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);
|
||||
}
|
||||
|
||||
@@ -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<Svg>(this, "assets/icons/RecordBorder.svg");
|
||||
icon->set_width(assign_player_card_icon_size, Unit::Dp);
|
||||
icon->set_height(assign_player_card_icon_size, Unit::Dp);
|
||||
icon->set_color(theme::color::TextDim);
|
||||
}
|
||||
|
||||
AssignPlayerCard::~AssignPlayerCard() {
|
||||
}
|
||||
|
||||
void AssignPlayerCard::update_player_card(int player_index) {
|
||||
static const float scale_anim_duration = 0.25f;
|
||||
bool is_assigned = recompinput::get_player_is_assigned(player_index);
|
||||
if (!is_assigned) {
|
||||
icon->set_scale_2D(1.0f, 1.0f);
|
||||
set_background_color(theme::color::Transparent);
|
||||
icon->set_color(theme::color::TextDim);
|
||||
icon->set_src("assets/icons/RecordBorder.svg");
|
||||
return;
|
||||
}
|
||||
|
||||
set_background_color(theme::color::PrimaryA20);
|
||||
|
||||
bool has_controller =recompinput::does_player_have_controller(player_index);
|
||||
|
||||
std::chrono::steady_clock::duration time_since_last_button_press = recompinput::get_player_time_since_last_button_press(player_index);
|
||||
auto millis = static_cast<float>(std::chrono::duration_cast<std::chrono::milliseconds>(time_since_last_button_press).count());
|
||||
float seconds = millis / 1000.0f;
|
||||
|
||||
if (seconds > 0 && seconds < scale_anim_duration) {
|
||||
float t = 1.0f - (seconds / scale_anim_duration);
|
||||
float scale = 1.0f + t * 0.15f;
|
||||
icon->set_scale_2D(scale, scale);
|
||||
icon->set_color(theme::color::Text, 200 + static_cast<int>(t * 55.0f));
|
||||
} else {
|
||||
icon->set_scale_2D(1.0f, 1.0f);
|
||||
icon->set_color(theme::color::Text, 200);
|
||||
}
|
||||
|
||||
if (has_controller) {
|
||||
icon->set_src("assets/icons/Cont.svg");
|
||||
} else {
|
||||
icon->set_src("assets/icons/Keyboard.svg");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static const float assignPlayersHFPaddingVert = 20.0f;
|
||||
static const float assignPlayersHFPaddingHorz = 20.0f;
|
||||
|
||||
static void set_button_side_styles(Element *el) {
|
||||
el->set_align_items(AlignItems::Center);
|
||||
el->set_width_auto();
|
||||
el->set_height_auto();
|
||||
el->set_flex_basis_auto();
|
||||
el->set_gap(8.0f);
|
||||
}
|
||||
|
||||
AssignPlayersModal::AssignPlayersModal(Element *parent) : Element(parent, 0, "div", false) {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
@@ -47,6 +120,12 @@ AssignPlayersModal::AssignPlayersModal(Element *parent) : Element(parent, 0, "di
|
||||
modal->set_border_color(theme::color::WhiteA20);
|
||||
modal->set_background_color(theme::color::ModalOverlay);
|
||||
|
||||
fake_focus_button = context.create_element<Element>(modal, 0, "button", false);
|
||||
fake_focus_button->set_position(Position::Absolute);
|
||||
fake_focus_button->set_width(0, Unit::Dp);
|
||||
fake_focus_button->set_height(0, Unit::Dp);
|
||||
fake_focus_button->set_opacity(0);
|
||||
|
||||
context.create_element<Label>(modal, "Assign Players", LabelStyle::Large);
|
||||
|
||||
player_elements_wrapper = context.create_element<Element>(modal, 0, "div", false);
|
||||
@@ -56,38 +135,76 @@ AssignPlayersModal::AssignPlayersModal(Element *parent) : Element(parent, 0, "di
|
||||
player_elements_wrapper->set_align_items(AlignItems::Center);
|
||||
player_elements_wrapper->set_width(100, Unit::Percent);
|
||||
player_elements_wrapper->set_padding(24, Unit::Dp);
|
||||
|
||||
Element* footer = context.create_element<Element>(modal, 0, "div", false);
|
||||
footer->set_display(Display::Flex);
|
||||
footer->set_position(Position::Relative);
|
||||
footer->set_flex_direction(FlexDirection::Row);
|
||||
footer->set_align_items(AlignItems::Center);
|
||||
footer->set_justify_content(JustifyContent::SpaceBetween);
|
||||
footer->set_width(100.0f, Unit::Percent);
|
||||
footer->set_height_auto();
|
||||
|
||||
footer->set_padding_top(assignPlayersHFPaddingVert);
|
||||
footer->set_padding_bottom(assignPlayersHFPaddingVert);
|
||||
footer->set_padding_left(assignPlayersHFPaddingHorz);
|
||||
footer->set_padding_right(assignPlayersHFPaddingHorz);
|
||||
|
||||
auto left = context.create_element<Container>(footer, FlexDirection::Row, JustifyContent::FlexStart, 0);
|
||||
set_button_side_styles(left);
|
||||
close_button = context.create_element<Button>(left, "Cancel", ButtonStyle::Tertiary);
|
||||
close_button->add_pressed_callback(recompinput::stop_player_assignment_and_close_modal);
|
||||
|
||||
auto right = context.create_element<Container>(footer, FlexDirection::Row, JustifyContent::FlexEnd, 0);
|
||||
retry_button = context.create_element<Button>(right, "Retry", ButtonStyle::Warning);
|
||||
retry_button->set_enabled(false);
|
||||
retry_button->add_pressed_callback(recompinput::start_player_assignment);
|
||||
|
||||
confirm_button = context.create_element<Button>(right, "Confirm", ButtonStyle::Primary);
|
||||
confirm_button->set_enabled(false);
|
||||
confirm_button->add_pressed_callback(recompinput::commit_player_assignment);
|
||||
set_button_side_styles(right);
|
||||
}
|
||||
|
||||
AssignPlayersModal::~AssignPlayersModal() {
|
||||
}
|
||||
|
||||
static void set_player_element_assigned(Element* player_element, bool assigned, bool as_controller) {
|
||||
if (assigned) {
|
||||
if (as_controller) {
|
||||
player_element->set_background_color(theme::color::PrimaryA50);
|
||||
} else {
|
||||
player_element->set_background_color(theme::color::SecondaryA50);
|
||||
}
|
||||
} else {
|
||||
player_element->set_background_color(theme::color::Transparent);
|
||||
}
|
||||
}
|
||||
|
||||
void AssignPlayersModal::process_event(const Event &e) {
|
||||
if (!is_open) {
|
||||
return;
|
||||
}
|
||||
if (e.type == EventType::Update) {
|
||||
if (!recompinput::is_player_assignment_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player_elements.empty() || player_elements.size() != recompinput::get_num_players()) {
|
||||
create_player_elements();
|
||||
}
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
set_player_element_assigned(player_elements[i], recompinput::get_player_is_assigned(i), recompinput::does_player_have_controller(i));
|
||||
player_elements[i]->update_player_card(i);
|
||||
}
|
||||
|
||||
if (!recompinput::is_player_assignment_active()) {
|
||||
if (recompinput::get_player_is_assigned(0)) {
|
||||
confirm_button->set_enabled(true);
|
||||
retry_button->set_enabled(true);
|
||||
if (was_assigning) {
|
||||
confirm_button->focus();
|
||||
}
|
||||
}
|
||||
|
||||
was_assigning = false;
|
||||
} else {
|
||||
fake_focus_button->focus();
|
||||
was_assigning = true;
|
||||
}
|
||||
|
||||
if (recompinput::get_player_is_assigned(0)) {
|
||||
confirm_button->set_enabled(true);
|
||||
retry_button->set_enabled(true);
|
||||
} else {
|
||||
confirm_button->set_enabled(false);
|
||||
retry_button->set_enabled(false);
|
||||
}
|
||||
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
@@ -98,18 +215,7 @@ void AssignPlayersModal::create_player_elements() {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
Element* player_element = context.create_element<Element>(player_elements_wrapper, 0, "div", false);
|
||||
player_element->set_display(Display::Flex);
|
||||
player_element->set_flex_direction(FlexDirection::Column);
|
||||
player_element->set_align_items(AlignItems::Center);
|
||||
player_element->set_justify_content(JustifyContent::Center);
|
||||
player_element->set_width(100);
|
||||
player_element->set_height(100);
|
||||
player_element->set_border_color(theme::color::BorderSoft);
|
||||
player_element->set_border_width(theme::border::width, Unit::Dp);
|
||||
player_element->set_border_radius(theme::border::radius_sm, Unit::Dp);
|
||||
player_element->set_background_color(theme::color::Transparent);
|
||||
|
||||
AssignPlayerCard* player_element = context.create_element<AssignPlayerCard>(player_elements_wrapper);
|
||||
player_elements.push_back(player_element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,34 @@
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_svg.h"
|
||||
#include "elements/ui_button.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class AssignPlayerCard : public Element {
|
||||
protected:
|
||||
bool is_open = false;
|
||||
Svg* icon = nullptr;
|
||||
|
||||
std::string_view get_type_name() override { return "AssignPlayerCard"; }
|
||||
public:
|
||||
AssignPlayerCard(Element *parent);
|
||||
virtual ~AssignPlayerCard();
|
||||
void update_player_card(int player_index);
|
||||
};
|
||||
|
||||
class AssignPlayersModal : public Element {
|
||||
protected:
|
||||
bool is_open = false;
|
||||
bool was_assigning = false;
|
||||
Element* player_elements_wrapper = nullptr;
|
||||
std::vector<Element*> player_elements = {};
|
||||
Element* fake_focus_button = nullptr;
|
||||
std::vector<AssignPlayerCard*> player_elements = {};
|
||||
|
||||
Button* close_button = nullptr;
|
||||
Button* retry_button = nullptr;
|
||||
Button* confirm_button = nullptr;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "AssignPlayersModal"; }
|
||||
|
||||
Reference in New Issue
Block a user