WIP code driven input mapping and player assignment

This commit is contained in:
thecozies
2025-07-20 17:42:57 -05:00
parent b1f0f1b3ac
commit b887dd99e7
25 changed files with 1589 additions and 136 deletions
+7 -1
View File
@@ -168,6 +168,10 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_assign_players_modal.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_example.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_controls.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config_page_controls_element.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
@@ -186,10 +190,12 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_icon_button.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_config_page.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_pill_button.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp
@@ -288,7 +294,7 @@ if (WIN32)
PROPERTIES
LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE"
LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE" # "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"
)
+14
View File
@@ -78,6 +78,20 @@
<panel class="config" data-model="debug_model">
<template src="config-menu__debug" />
</panel>
<tab class="tab" id="tab_controls2">
<div>Controls new</div>
<div class="tab__indicator"></div>
</tab>
<panel class="config">
<recomp-config-page-controls />
</panel>
<tab class="tab" id="tab_example">
<div>Example</div>
<div class="tab__indicator"></div>
</tab>
<panel class="config">
<recomp-config-page-example />
</panel>
</tabset>
<div class="config__icon-buttons">
<button
+3
View File
@@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.72625 23.3925C8.13475 23.0659 8.61021 22.3958 9.032 21.7129C9.78396 20.4953 11.0499 19.6669 12.4578 19.6669H19.5422C20.9501 19.6669 22.216 20.4953 22.968 21.7129C23.3898 22.3958 23.8653 23.0659 24.2738 23.3925C25.2857 24.2015 26.6008 24.2035 27.6585 23.3925C28.4268 22.8033 29 21.7232 29 20.2983C29 18.8735 28.6384 12.9609 27.6585 9.98045C26.6786 7 23.4285 7 16 7C8.57147 7 5.32145 7 4.34153 9.98045C3.36162 12.9609 3 18.8735 3 20.2983C3 21.7232 3.5732 22.8033 4.34153 23.3925C5.39916 24.2035 6.71431 24.2015 7.72625 23.3925Z" stroke="white" stroke-width="4"/>
</svg>

After

Width:  |  Height:  |  Size: 676 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 3C24.091 3.00019 28.7417 10.0042 28.9902 16.082C29.0372 17.2318 28.8162 18.5326 28.2207 19.4746C27.7079 20.2859 26.8271 21 25 21H7C5.17292 21 4.29117 20.2859 3.77832 19.4746C3.18306 18.5326 2.96285 17.2316 3.00977 16.082C3.25811 10.0041 7.90882 3 16 3Z" stroke="white" stroke-width="4" stroke-linecap="round"/>
<circle cx="16" cy="14.5" r="2.5" fill="white"/>
<circle cx="22.5" cy="14.5" r="2.5" fill="white"/>
<circle cx="9.5" cy="14.5" r="2.5" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 577 B

+43 -1
View File
@@ -62,8 +62,19 @@ namespace recomp {
};
#undef DEFINE_INPUT
// What type of source an input comes from (SDL_Scancode, SDL_GameControllerButton, SDL_GameControllerAxis, SDL_BUTTON, etc.)
enum class InputType {
None = 0, // Using zero for None ensures that default initialized InputFields are unbound.
Keyboard,
Mouse,
ControllerDigital,
ControllerAnalog // Axis input_id values are the SDL value + 1
};
// A single input. Combines the source of the input (see InputType) and a specific key/button/axis.
struct InputField {
uint32_t input_type;
InputType input_type;
// Represents a single source input. e.g. A keyboard's shift key, or a controller's R trigger
int32_t input_id;
std::string to_string() const;
auto operator<=>(const InputField& rhs) const = default;
@@ -220,6 +231,37 @@ namespace recomp {
bool game_input_disabled();
bool all_input_disabled();
}
namespace recompinput {
struct BindingState {
bool is_scanning = false;
bool found_binding = false;
int player_index = -1;
recomp::GameInput game_input = recomp::GameInput::COUNT;
int binding_index = -1;
recomp::InputField new_binding = {};
};
void start_scanning_for_binding(int player_index, recomp::GameInput game_input, int binding_index);
void stop_scanning_for_binding();
BindingState& get_binding_state();
int get_num_players();
bool get_player_is_assigned(int player_index);
struct PlayerAssignmentState {
bool is_assigning = false;
int player_index = 0;
};
void start_player_assignment(void);
void stop_player_assignment(void);
bool is_player_assignment_active();
bool does_player_have_controller(int player_index);
}
#endif
+273 -109
View File
@@ -9,6 +9,7 @@
#include "SDL.h"
#include "promptfont.h"
#include "GamepadMotion.hpp"
#include "../ui/ui_assign_players_modal.h"
constexpr float axis_threshold = 0.5f;
@@ -23,6 +24,12 @@ 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;
@@ -31,9 +38,9 @@ static struct {
std::mutex controllers_mutex;
std::vector<SDL_GameController*> detected_controllers{};
std::vector<recomp::ControllerOption> detected_controller_options{};
std::array<SDL_GameController*, 4> assigned_controllers{}; // Only used when Multiplayer is enabled.
std::array<AssignedPlayer, 4> assigned_controllers{}; // Only used when Multiplayer is enabled.
std::unordered_map<SDL_JoystickID, ControllerState> controller_states;
bool single_controller = true;
bool single_controller = false;
std::array<float, 2> rotation_delta{};
std::array<float, 2> mouse_delta{};
@@ -52,13 +59,121 @@ static struct {
std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
std::atomic<recomp::InputField> scanned_input;
enum class InputType {
None = 0, // Using zero for None ensures that default initialized InputFields are unbound.
Keyboard,
Mouse,
ControllerDigital,
ControllerAnalog // Axis input_id values are the SDL value + 1
};
static recompinput::BindingState binding_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;
binding_state.found_binding = false;
binding_state.player_index = player_index;
binding_state.game_input = game_input;
binding_state.binding_index = binding_index;
}
void recompinput::stop_scanning_for_binding() {
binding_state.is_scanning = false;
binding_state.found_binding = false;
binding_state.player_index = -1;
binding_state.game_input = recomp::GameInput::COUNT;
binding_state.binding_index = -1;
}
recompinput::BindingState& recompinput::get_binding_state() {
return binding_state;
}
int recompinput::get_num_players() {
return static_cast<int>(InputState.assigned_controllers.size());
}
bool recompinput::get_player_is_assigned(int player_index) {
if (player_index < 0 || player_index >= recompinput::get_num_players()) {
return false;
}
return InputState.assigned_controllers[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;
}
}
void recompinput::stop_player_assignment() {
player_assignment_state.is_assigning = false;
player_assignment_state.player_index = -1;
recompui::assign_players_modal->close();
}
bool recompinput::is_player_assignment_active() {
return player_assignment_state.is_assigning;
}
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;
}
void process_player_assignment(SDL_Event* event) {
if (!player_assignment_state.is_assigning) {
return;
}
recomp::refresh_controller_options();
switch (event->type) {
case SDL_EventType::SDL_KEYDOWN: {
SDL_KeyboardEvent* keyevent = &event->key;
switch (keyevent->keysym.scancode) {
case SDL_Scancode::SDL_SCANCODE_ESCAPE:
// TODO: Restore previous assignment?
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.player_index++;
printf("Assigned keyboard to player %d\n", player_assignment_state.player_index - 1);
break;
}
break;
}
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: {
SDL_ControllerButtonEvent* button_event = &event->cbutton;
SDL_JoystickID joystick_id = button_event->which;
auto controller_state = InputState.controller_states[joystick_id];
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) {
can_be_mapped = false;
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;
player_assignment_state.player_index++;
printf("Assigned controller %d to player %d\n", joystick_id, player_assignment_state.player_index - 1);
}
break;
}
}
if (player_assignment_state.player_index >= recompinput::get_num_players()) {
recompinput::stop_player_assignment();
}
}
void set_scanned_input(recomp::InputField value) {
scanning_device.store(recomp::InputDevice::COUNT);
@@ -125,7 +240,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
recomp::cancel_scanning_input();
}
else if (scanning_device == recomp::InputDevice::Keyboard) {
set_scanned_input({ (uint32_t)InputType::Keyboard, keyevent->keysym.scancode });
set_scanned_input({ recomp::InputType::Keyboard, keyevent->keysym.scancode });
}
}
else {
@@ -142,6 +257,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
printf("Controller added: %d\n", controller_event->which);
if (controller != nullptr) {
printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)));
printf(" Path: %s\n", SDL_JoystickPath(SDL_GameControllerGetJoystick(controller)));
ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))];
state.controller = controller;
@@ -182,8 +298,8 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
// note - magic number: 0 is InputType::None
if ((menuToggleBinding0.input_type != 0 && event->cbutton.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != 0 && event->cbutton.button == menuToggleBinding1.input_id)) {
if ((menuToggleBinding0.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding1.input_id)) {
recomp::cancel_scanning_input();
}
else if (scanning_device == recomp::InputDevice::Controller) {
@@ -199,7 +315,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
break;
}
set_scanned_input({ (uint32_t)InputType::ControllerDigital, button_event->button });
set_scanned_input({ recomp::InputType::ControllerDigital, button_event->button });
}
}
else {
@@ -225,7 +341,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
set_stick_return_event.user.data2 = nullptr;
recompui::queue_event(set_stick_return_event);
set_scanned_input({ (uint32_t)InputType::ControllerAnalog, axis_event->axis + 1 });
set_scanned_input({ recomp::InputType::ControllerAnalog, axis_event->axis + 1 });
}
else if (axis_value < -axis_threshold) {
SDL_Event set_stick_return_event;
@@ -235,7 +351,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
set_stick_return_event.user.data2 = nullptr;
recompui::queue_event(set_stick_return_event);
set_scanned_input({ (uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1 });
set_scanned_input({ recomp::InputType::ControllerAnalog, -axis_event->axis - 1 });
}
}
else {
@@ -304,6 +420,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
queue_if_enabled(event);
break;
}
process_player_assignment(event);
return false;
}
@@ -340,144 +457,172 @@ constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_NORTH = SDL_CONTROLLER_
const recomp::DefaultN64Mappings recomp::default_n64_keyboard_mappings = {
.a = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_SPACE}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_SPACE}
},
.b = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT}
},
.l = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_E}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_E}
},
.r = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_R}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_R}
},
.z = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_Q}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_Q}
},
.start = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
},
.c_left = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LEFT}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_LEFT}
},
.c_right = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RIGHT}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RIGHT}
},
.c_up = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_UP}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_UP}
},
.c_down = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_DOWN}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_DOWN}
},
.dpad_left = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_J}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_J}
},
.dpad_right = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_L}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_L}
},
.dpad_up = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_I}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_I}
},
.dpad_down = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_K}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_K}
},
.analog_left = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_A}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_A}
},
.analog_right = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_D}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_D}
},
.analog_up = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_W}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_W}
},
.analog_down = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_S}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_S}
},
.toggle_menu = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_ESCAPE}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_ESCAPE}
},
.accept_menu = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
},
.apply_menu = {
{.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_F}
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F}
}
};
const recomp::DefaultN64Mappings recomp::default_n64_controller_mappings = {
.a = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
},
.b = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
},
.l = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
},
.r = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1},
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1},
},
.z = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1},
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1},
},
.start = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START},
},
.c_left = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)},
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH},
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH},
},
.c_right = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1},
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST},
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST},
},
.c_up = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)},
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSTICK},
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSTICK},
},
.c_down = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1},
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
},
.dpad_left = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT},
},
.dpad_right = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
},
.dpad_up = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP},
},
.dpad_down = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN},
},
.analog_left = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)},
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)},
},
.analog_right = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1},
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1},
},
.analog_up = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)},
{.input_type = InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)},
},
.analog_down = {
{.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1},
{.input_type = InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1},
},
.toggle_menu = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_BACK},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_BACK},
},
.accept_menu = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
},
.apply_menu = {
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
{.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}
}
};
// Returns true if the guid can be fetched.
static bool get_controller_guid_from_sdl_controller(SDL_GameController* controller, recomp::ControllerGUID *guid) {
if (controller == nullptr) {
return false;
}
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
if (joystick == nullptr) {
return false;
}
Uint16 vendor, product, version, crc16;
const char *joystick_name = SDL_JoystickName(joystick);
const char *joystick_serial = SDL_JoystickGetSerial(joystick);
std::string joystick_name_string = joystick_name != nullptr ? std::string(joystick_name) : "Unknown controller";
SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick);
int joystick_player_index = SDL_JoystickGetPlayerIndex(joystick);
SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16);
*guid = { joystick_serial != nullptr ? std::string(joystick_serial) : std::string(), vendor, product, version, crc16, joystick_player_index };
return true;
}
static bool compare_controller_guid(const recomp::ControllerGUID& a, const recomp::ControllerGUID& b) {
return a.vendor == b.vendor && a.product == b.product && a.version == b.version && a.crc16 == b.crc16 && a.serial == b.serial;
}
void recomp::poll_inputs() {
InputState.keys = SDL_GetKeyboardState(&InputState.numkeys);
InputState.keymod = SDL_GetModState();
static bool first_poll = true;
{
std::lock_guard lock{ InputState.controllers_mutex };
@@ -495,51 +640,63 @@ void recomp::poll_inputs() {
}
}
// Assign controllers based on configuration.
InputState.assigned_controllers.fill(nullptr);
// FIXME: Use active player count instead of iterating on all possible players.
Uint16 vendor, product, version, crc16;
for (size_t i = 0; i < 4; i++) {
recomp::ControllerGUID controller_guid = recomp::get_input_controller_guid(i);
int min_index_difference = INT_MAX;
size_t j = 0;
while (j < free_controllers.size()) {
SDL_GameController *controller = InputState.detected_controllers[free_controllers[j]];
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
if (joystick != nullptr) {
SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick);
const char *joystick_serial = SDL_JoystickGetSerial(joystick);
SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16);
if (first_poll) {
first_poll = false;
bool empty_serials = controller_guid.serial.empty() && joystick_serial == nullptr;
bool matching_serials = joystick_serial != nullptr && strcmp(joystick_serial, controller_guid.serial.c_str()) == 0;
if (vendor == controller_guid.vendor && product == controller_guid.product && version == controller_guid.version && crc16 == controller_guid.crc16 && (empty_serials || matching_serials)) {
// The controller seems to be a match, but we use the controller with the least difference in player index to sort out potential duplicates.
int joystick_player_index = SDL_JoystickGetPlayerIndex(joystick);
int index_difference = abs(controller_guid.player_index - joystick_player_index);
if (min_index_difference > index_difference) {
InputState.assigned_controllers[i] = controller;
min_index_difference = index_difference;
free_controllers.erase(free_controllers.begin() + j);
continue;
}
// Assign controllers based on configuration.
for (auto& player : InputState.assigned_controllers) {
player.controller = nullptr;
player.is_assigned = false;
player.keyboard_enabled = false;
}
// FIXME: Use active player count instead of iterating on all possible players.
Uint16 vendor, product, version, crc16;
for (size_t i = 0; i < 4; i++) {
recomp::ControllerGUID controller_guid = recomp::get_input_controller_guid(i);
int min_index_difference = INT_MAX;
size_t j = 0;
while (j < free_controllers.size()) {
SDL_GameController *controller = InputState.detected_controllers[free_controllers[j]];
recomp::ControllerGUID controller_guid_from_sdl;
if (!get_controller_guid_from_sdl_controller(controller, &controller_guid_from_sdl)) {
// If we can't get the controller guid, skip this controller.
j++;
continue;
}
if (!compare_controller_guid(controller_guid, controller_guid_from_sdl)) {
// If the controller guid doesn't match, skip this controller.
j++;
continue;
}
// The controller seems to be a match, but we use the controller with the least difference in player index to sort out potential duplicates.
int index_difference = abs(controller_guid.player_index - controller_guid_from_sdl.player_index);
if (min_index_difference > index_difference) {
InputState.assigned_controllers[i].controller = controller;
min_index_difference = index_difference;
free_controllers.erase(free_controllers.begin() + j);
continue;
}
j++;
}
j++;
}
}
// Do a second pass to assign controllers that are currently unused to the remaining players that failed to be assigned a controller.
for (int i = 0; i < 4; i++) {
if (InputState.assigned_controllers[i] != nullptr) {
if (i == 0) {
InputState.assigned_controllers[i].keyboard_enabled = true;
}
if (InputState.assigned_controllers[i].controller != nullptr) {
continue;
}
if (!free_controllers.empty()) {
// Assign a free controller only.
InputState.assigned_controllers[i] = InputState.detected_controllers[free_controllers.front()];
InputState.assigned_controllers[i].controller = InputState.detected_controllers[free_controllers.front()];
free_controllers.erase(free_controllers.begin());
}
else {
@@ -610,8 +767,8 @@ void recomp::update_rumble() {
}
}
else {
if (InputState.assigned_controllers[i] != nullptr) {
SDL_GameControllerRumble(InputState.assigned_controllers[i], 0, rumble_strength, duration);
if (InputState.assigned_controllers[i].controller != nullptr) {
SDL_GameControllerRumble(InputState.assigned_controllers[i].controller, 0, rumble_strength, duration);
}
}
}
@@ -630,8 +787,8 @@ bool controller_button_state(int controller_num, int32_t input_id) {
}
}
else {
if (InputState.assigned_controllers[controller_num] != nullptr) {
ret |= SDL_GameControllerGetButton(InputState.assigned_controllers[controller_num], button);
if (InputState.assigned_controllers[controller_num].controller != nullptr) {
ret |= SDL_GameControllerGetButton(InputState.assigned_controllers[controller_num].controller, button);
}
}
}
@@ -671,8 +828,8 @@ float controller_axis_state(int controller_num, int32_t input_id, bool allow_sup
}
}
else {
if (InputState.assigned_controllers[controller_num] != nullptr) {
gather_axis_state(InputState.assigned_controllers[controller_num]);
if (InputState.assigned_controllers[controller_num].controller != nullptr) {
gather_axis_state(InputState.assigned_controllers[controller_num].controller);
}
}
}
@@ -683,7 +840,7 @@ float controller_axis_state(int controller_num, int32_t input_id, bool allow_sup
}
float recomp::get_input_analog(int controller_num, const recomp::InputField& field) {
switch ((InputType)field.input_type) {
switch (field.input_type) {
case InputType::Keyboard:
if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) {
if (should_override_keystate(static_cast<SDL_Scancode>(field.input_id), InputState.keymod)) {
@@ -713,7 +870,7 @@ float recomp::get_input_analog(int controller_num, const std::span<const recomp:
}
bool recomp::get_input_digital(int controller_num, const recomp::InputField& field) {
switch ((InputType)field.input_type) {
switch (field.input_type) {
case InputType::Keyboard:
if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) {
if (should_override_keystate(static_cast<SDL_Scancode>(field.input_id), InputState.keymod)) {
@@ -813,7 +970,7 @@ bool recomp::game_input_disabled() {
bool recomp::all_input_disabled() {
// Disable all input if an input is being polled.
return scanning_device != recomp::InputDevice::COUNT;
return scanning_device != recomp::InputDevice::COUNT || recompinput::is_player_assignment_active();
}
bool recomp::get_single_controller_mode() {
@@ -976,7 +1133,7 @@ std::string controller_axis_to_string(int axis) {
}
std::string recomp::InputField::to_string() const {
switch ((InputType)input_type) {
switch (input_type) {
case InputType::None:
return "";
case InputType::ControllerDigital:
@@ -986,15 +1143,22 @@ std::string recomp::InputField::to_string() const {
case InputType::Keyboard:
return keyboard_input_to_string((SDL_Scancode)input_id);
default:
return std::to_string(input_type) + "," + std::to_string(input_id);
return std::to_string((uint32_t)input_type) + "," + std::to_string(input_id);
}
}
void recomp::refresh_controller_options() {
// TODO: Is it okay to use this to make sure the detected controllers are up to date here?
recomp::poll_inputs();
std::lock_guard lock{ InputState.controllers_mutex };
InputState.detected_controllers.clear();
for (const auto& [id, state] : InputState.controller_states) {
(void)id; // Avoid unused variable warning.
SDL_GameController* controller = state.controller;
if (controller != nullptr) {
InputState.detected_controllers.push_back(controller);
}
}
InputState.detected_controller_options.clear();
for (SDL_GameController* controller : InputState.detected_controllers) {
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
@@ -1017,4 +1181,4 @@ void recomp::refresh_controller_options() {
const std::vector<recomp::ControllerOption> &recomp::get_controller_options() {
return InputState.detected_controller_options;
}
}
+23 -17
View File
@@ -4,29 +4,13 @@
namespace recompui {
static const float padding = 8.0f;
static const float height = 56.0f - (theme::border::width * 2.0f);
BindingButton::BindingButton(Element *parent, const std::string &mapped_binding) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
this->mapped_binding = mapped_binding;
enable_focus();
apply_sizing_styling(this);
set_display(Display::Flex);
set_position(Position::Relative);
set_flex_grow(1.0f);
set_flex_shrink(1.0f);
set_flex_basis(100.0f, recompui::Unit::Percent);
set_align_items(AlignItems::Center);
set_justify_content(JustifyContent::Center);
set_width(100.0f, recompui::Unit::Percent);
set_height(height);
set_padding(padding);
set_border_width(theme::border::width);
set_border_radius(theme::border::radius_sm);
set_border_color(theme::color::WhiteA5);
set_background_color(theme::color::WhiteA5);
set_color(theme::color::TextDim);
@@ -62,6 +46,28 @@ namespace recompui {
apply_recording_style();
}
void BindingButton::apply_sizing_styling(Element *el) {
const float height = 56.0f - (theme::border::width * 2.0f);
el->set_display(Display::Flex);
el->set_position(Position::Relative);
el->set_flex_grow(1.0f);
el->set_flex_shrink(1.0f);
el->set_flex_basis(100.0f, recompui::Unit::Percent);
el->set_align_items(AlignItems::Center);
el->set_justify_content(JustifyContent::Center);
el->set_width(100.0f, recompui::Unit::Percent);
el->set_height(height);
el->set_padding(padding);
el->set_border_width(theme::border::width);
el->set_border_radius(theme::border::radius_sm);
el->set_border_color(theme::color::Transparent);
el->set_background_color(theme::color::Transparent);
}
void BindingButton::apply_recording_style() {
recording_parent->set_display(Display::Flex);
recording_parent->set_position(Position::Absolute);
+2
View File
@@ -34,6 +34,8 @@ namespace recompui {
Style* get_focus_style() { return &focus_style; }
Style* get_disabled_style() { return &disabled_style; }
Style* get_hover_disabled_style() { return &hover_disabled_style; }
// This is exposed for placeholder elements
static void apply_sizing_styling(Element *el);
private:
void apply_recording_style();
void apply_binding_style();
+125
View File
@@ -0,0 +1,125 @@
#include "ui_config_page.h"
#include "ui_theme.h"
#include "ui_container.h"
namespace recompui {
static const float headerFooterPaddingVert = 20.0f;
static const float headerFooterPaddingHorz = 20.0f;
static void set_header_footer_side_styles(Element *el) {
el->set_align_items(AlignItems::Center);
// el->set_width(100.0f, Unit::Percent);
el->set_width_auto();
el->set_height_auto();
el->set_flex_basis_auto();
el->set_gap(8.0f);
}
ConfigHeaderFooter::ConfigHeaderFooter(Element *parent, bool is_header) : Element(parent, 0, "div", false) {
set_display(Display::Flex);
set_position(Position::Relative);
set_flex_direction(FlexDirection::Row);
set_align_items(AlignItems::Center);
set_justify_content(JustifyContent::SpaceBetween);
set_width(100.0f, Unit::Percent);
set_height_auto();
set_padding_top(headerFooterPaddingVert);
set_padding_bottom(headerFooterPaddingVert);
set_padding_left(headerFooterPaddingHorz);
set_padding_right(headerFooterPaddingHorz);
set_background_color(theme::color::BGShadow);
const float border_width = theme::border::width;
const recompui::theme::color border_color = theme::color::BorderSoft;
if (is_header) {
set_border_bottom_width(border_width);
set_border_bottom_color(border_color);
} else {
set_border_top_width(border_width);
set_border_top_color(border_color);
set_border_bottom_left_radius(theme::border::radius_lg);
set_border_bottom_right_radius(theme::border::radius_lg);
}
ContextId context = get_current_context();
left = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexStart, 0);
set_header_footer_side_styles(left);
right = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexEnd, 0);
set_header_footer_side_styles(right);
}
static void set_config_body_side_styles(Element *el) {
el->set_flex_grow(1.0f);
el->set_flex_shrink(1.0f);
el->set_flex_basis(100.0f, Unit::Percent);
el->set_width_auto();
el->set_height_auto();
el->set_padding(16);
}
ConfigBody::ConfigBody(Element *parent) : Element(parent, 0, "div", false) {
set_display(Display::Flex);
set_position(Position::Relative);
set_flex_grow(1.0f);
set_flex_shrink(1.0f);
set_flex_basis_auto();
set_flex_direction(FlexDirection::Row);
set_width(100.0f, Unit::Percent);
ContextId context = get_current_context();
left = context.create_element<Element>(this, 0, "div", false);
set_config_body_side_styles(left);
right = context.create_element<Element>(this, 0, "div", false);
set_config_body_side_styles(right);
}
ConfigPage::ConfigPage(Element *parent) : Element(parent, 0, "div", false) {
set_display(Display::Flex);
set_position(Position::Relative);
set_flex_grow(1.0f);
set_flex_shrink(1.0f);
set_flex_basis(100.0f, Unit::Percent);
set_flex_direction(FlexDirection::Column);
set_justify_content(JustifyContent::SpaceBetween);
set_width(100.0f, Unit::Percent);
set_height(100.0f, Unit::Percent);
set_border_top_width(theme::border::width);
set_border_top_color(theme::color::BorderSoft);
ContextId context = get_current_context();
header = context.create_element<ConfigHeaderFooter>(this, true);
header->set_visibility(Visibility::Hidden);
body = context.create_element<ConfigBody>(this);
set_border_bottom_left_radius(theme::border::radius_lg);
set_border_bottom_right_radius(theme::border::radius_lg);
footer = context.create_element<ConfigHeaderFooter>(this, false);
footer->set_visibility(Visibility::Hidden);
}
ConfigHeaderFooter* ConfigPage::add_header() {
header->set_visibility(Visibility::Visible);
return header;
}
ConfigHeaderFooter* ConfigPage::add_footer() {
footer->set_visibility(Visibility::Visible);
set_border_bottom_left_radius(0);
set_border_bottom_right_radius(0);
return footer;
}
} // namespace recompui
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include "ui_element.h"
namespace recompui {
class ConfigHeaderFooter : public Element {
protected:
Element *left;
Element *right;
bool is_header;
std::string_view get_type_name() override { return "ConfigHeaderFooter"; }
public:
ConfigHeaderFooter(Element *parent, bool is_header);
Element *get_left() { return left; }
Element *get_right() { return right; }
};
class ConfigBody : public Element {
protected:
Element *left;
Element *right;
std::string_view get_type_name() override { return "ConfigBody"; }
public:
ConfigBody(Element *parent);
Element *get_left() { return left; }
Element *get_right() { return right; }
};
class ConfigPage : public Element {
protected:
ConfigHeaderFooter *header = nullptr;
ConfigBody *body;
ConfigHeaderFooter *footer = nullptr;
std::string_view get_type_name() override { return "ConfigPage"; }
public:
ConfigPage(Element *parent);
ConfigHeaderFooter *add_header();
ConfigHeaderFooter *add_footer();
ConfigHeaderFooter *get_header() { return header; };
ConfigBody *get_body() { return body; };
ConfigHeaderFooter *get_footer() { return footer; };
private:
};
} // namespace recompui
+205
View File
@@ -0,0 +1,205 @@
#include "ui_pill_button.h"
#include "ui_label.h"
#include <cassert>
namespace recompui {
static constexpr float pill_padding = 16.0f;
PillButton::PillButton(Element *parent, const std::string &text, const std::string &svg_src, ButtonStyle style, PillButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
this->style = style;
this->size = size;
// Borders add width to the button, so this subtracts from the base size to bring it back to the expected size.
float float_size_internal = static_cast<float>(size) - (theme::border::width * 2.0f);
enable_focus();
set_display(Display::Flex);
set_align_items(AlignItems::Center);
set_justify_content(JustifyContent::Center);
set_min_width(float_size_internal);
set_width_auto();
set_padding_right(pill_padding);
set_padding_left(pill_padding);
set_height(float_size_internal);
set_min_height(float_size_internal);
set_max_height(float_size_internal);
set_border_width(theme::border::width);
set_border_radius(float_size_internal * 0.5f);
set_border_color(theme::color::Transparent);
set_cursor(Cursor::Pointer);
set_color(theme::color::TextDim);
set_tab_index(TabIndex::Auto);
hover_style.set_color(theme::color::Text);
focus_style.set_color(theme::color::Text);
disabled_style.set_color(theme::color::TextDim);
disabled_style.set_cursor(Cursor::None);
disabled_style.set_opacity(0.5f);
hover_disabled_style.set_color(theme::color::TextDim);
ContextId context = get_current_context();
bool has_svg = !svg_src.empty();
bool has_text = !text.empty();
if (has_svg) {
float icon_size = 0;
switch (size) {
case PillButtonSize::Mini:
icon_size = 16.0f;
break;
case PillButtonSize::Small:
icon_size = 24.0f;
break;
case PillButtonSize::Medium:
icon_size = 32.0f;
break;
case PillButtonSize::Large:
default:
icon_size = 32.0f;
break;
case PillButtonSize::XLarge:
icon_size = 40.0f;
break;
}
svg = context.create_element<Svg>(this, svg_src);
svg->set_width(icon_size);
svg->set_image_color(theme::color::TextDim);
}
if (has_text) {
auto label = context.create_element<Label>(this, text, LabelStyle::Normal);
if (has_svg) {
label->set_margin_left(8.0f);
}
}
apply_button_style(style);
}
void PillButton::process_event(const Event &e) {
switch (e.type) {
case EventType::Click:
if (is_enabled()) {
for (const auto &function : pressed_callbacks) {
function();
}
}
break;
case EventType::Hover:
{
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
set_style_enabled(hover_state, hover_active);
if (svg != nullptr && !has_override_text_color) {
svg->set_image_color(hover_active ? theme::color::Text : theme::color::TextDim);
}
}
break;
case EventType::Enable:
{
bool enable_active = std::get<EventEnable>(e.variant).active;
set_style_enabled(disabled_state, !enable_active);
if (enable_active) {
set_cursor(Cursor::Pointer);
set_focusable(true);
if (svg != nullptr && !has_override_text_color) {
svg->set_image_color(theme::color::TextDim);
}
}
else {
set_cursor(Cursor::None);
set_focusable(false);
if (svg != nullptr && !has_override_text_color) {
svg->set_image_color(theme::color::TextDim);
}
}
}
break;
case EventType::Focus:
{
bool focus_active = std::get<EventFocus>(e.variant).active;
set_style_enabled(focus_state, focus_active);
if (svg != nullptr && !has_override_text_color) {
svg->set_image_color(focus_active ? theme::color::Text : theme::color::TextDim);
}
}
break;
case EventType::Update:
break;
default:
assert(false && "Unknown event type.");
break;
}
}
void PillButton::add_pressed_callback(std::function<void()> callback) {
pressed_callbacks.emplace_back(callback);
}
void PillButton::apply_button_style(ButtonStyle new_style) {
style = new_style;
switch (style) {
case ButtonStyle::Primary: {
apply_theme_style(theme::color::Primary);
break;
}
case ButtonStyle::Secondary: {
apply_theme_style(theme::color::Secondary);
break;
}
case ButtonStyle::Tertiary: {
apply_theme_style(theme::color::Text);
break;
}
case ButtonStyle::Success: {
apply_theme_style(theme::color::Success);
break;
}
case ButtonStyle::Warning: {
apply_theme_style(theme::color::Warning);
break;
}
case ButtonStyle::Danger: {
apply_theme_style(theme::color::Danger);
break;
}
case ButtonStyle::Basic: {
apply_theme_style(theme::color::Text, true);
break;
}
default:
assert(false && "Unknown button style.");
break;
}
}
void PillButton::apply_theme_style(recompui::theme::color color, bool is_basic, bool with_text) {
const uint8_t background_opacity = is_basic ? 0 : 13;
const uint8_t border_opacity = is_basic ? 0 : 204;
const uint8_t background_hover_opacity = 77;
const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255;
has_override_text_color = with_text;
set_border_color(color, border_opacity);
set_background_color(color, background_opacity);
hover_style.set_border_color(color, border_hover_opacity);
hover_style.set_background_color(color, background_hover_opacity);
focus_style.set_border_color(color, border_hover_opacity);
focus_style.set_background_color(color, background_hover_opacity);
if (with_text) {
set_color(color);
if (svg != nullptr) {
svg->set_image_color(color);
}
}
add_style(&hover_style, hover_state);
add_style(&focus_style, focus_state);
add_style(&disabled_style, disabled_state);
add_style(&hover_disabled_style, { hover_state, disabled_state });
}
};
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "ui_element.h"
#include "ui_button.h"
#include "ui_svg.h"
namespace recompui {
enum class PillButtonSize {
Mini = 20, // 20x20 (Inline with body text)
Small = 32, // 32x32
Medium = 48, // 48x48
Large = 56, // 56x56
Default = PillButtonSize::Large,
XLarge = 72, // 72x72
};
class PillButton : public Element {
protected:
ButtonStyle style = ButtonStyle::Primary;
PillButtonSize size = PillButtonSize::Default;
Style hover_style;
Style focus_style;
Style disabled_style;
Style hover_disabled_style;
std::list<std::function<void()>> pressed_callbacks;
Svg *svg = nullptr;
bool has_override_text_color = false;
// Element overrides.
virtual void process_event(const Event &e) override;
std::string_view get_type_name() override { return "PillButton"; }
public:
PillButton(Element *parent, const std::string &text, const std::string &svg_src, ButtonStyle style, PillButtonSize size = PillButtonSize::Default);
void add_pressed_callback(std::function<void()> callback);
Style* get_hover_style() { return &hover_style; }
Style* get_focus_style() { return &focus_style; }
Style* get_disabled_style() { return &disabled_style; }
Style* get_hover_disabled_style() { return &hover_disabled_style; }
void apply_button_style(ButtonStyle new_style);
void apply_theme_style(recompui::theme::color color, bool is_basic = false, bool with_text = false);
};
} // namespace recompui
+17
View File
@@ -107,6 +107,15 @@ constexpr ThemeColorArray get_default_theme_color_array() {
colors[(std::size_t)ThemeColor::BW75] = recompui::Color{191, 191, 191, 255};
colors[(std::size_t)ThemeColor::BW90] = recompui::Color{229, 229, 229, 255};
colors[(std::size_t)ThemeColor::Player1] = recompui::Color{0, 185, 253, 255};
colors[(std::size_t)ThemeColor::Player2] = recompui::Color{253, 76, 0, 255};
colors[(std::size_t)ThemeColor::Player3] = recompui::Color{253, 194, 0, 255};
colors[(std::size_t)ThemeColor::Player4] = recompui::Color{147, 253, 0, 255};
colors[(std::size_t)ThemeColor::Player5] = recompui::Color{0, 253, 164, 255};
colors[(std::size_t)ThemeColor::Player6] = recompui::Color{253, 0, 189, 255};
colors[(std::size_t)ThemeColor::Player7] = recompui::Color{140, 74, 255, 255};
colors[(std::size_t)ThemeColor::Player8] = recompui::Color{255, 186, 118, 255};
return colors;
}
@@ -193,6 +202,14 @@ constexpr ThemeColorNameArray get_default_theme_color_names() {
names[(std::size_t)ThemeColor::BW50] = "BW50";
names[(std::size_t)ThemeColor::BW75] = "BW75";
names[(std::size_t)ThemeColor::BW90] = "BW90";
names[(std::size_t)ThemeColor::Player1] = "Player1";
names[(std::size_t)ThemeColor::Player2] = "Player2";
names[(std::size_t)ThemeColor::Player3] = "Player3";
names[(std::size_t)ThemeColor::Player4] = "Player4";
names[(std::size_t)ThemeColor::Player5] = "Player5";
names[(std::size_t)ThemeColor::Player6] = "Player6";
names[(std::size_t)ThemeColor::Player7] = "Player7";
names[(std::size_t)ThemeColor::Player8] = "Player8";
return names;
}
+8
View File
@@ -86,6 +86,14 @@ namespace recompui {
BW50,
BW75,
BW90,
Player1,
Player2,
Player3,
Player4,
Player5,
Player6,
Player7,
Player8,
size,
};
+145
View File
@@ -0,0 +1,145 @@
#include "ui_assign_players_modal.h"
#include "elements/ui_label.h"
#include "recomp_ui.h"
namespace recompui {
recompui::ContextId assign_players_modal_context;
AssignPlayersModal::AssignPlayersModal(Element *parent) : Element(parent, 0, "div", false) {
recompui::ContextId context = get_current_context();
set_display(Display::Flex);
set_flex_direction(FlexDirection::Column);
set_background_color(theme::color::Transparent);
set_display(Display::None);
Element* modal_overlay = context.create_element<Element>(this);
modal_overlay->set_background_color(theme::color::BGOverlay);
modal_overlay->set_position(Position::Absolute);
modal_overlay->set_top(0);
modal_overlay->set_right(0);
modal_overlay->set_bottom(0);
modal_overlay->set_left(0);
Element* modal_whole_page_wrapper = context.create_element<Element>(this);
modal_whole_page_wrapper->set_display(Display::Flex);
modal_whole_page_wrapper->set_position(Position::Absolute);
modal_whole_page_wrapper->set_top(0);
modal_whole_page_wrapper->set_right(0);
modal_whole_page_wrapper->set_bottom(0);
modal_whole_page_wrapper->set_left(0);
modal_whole_page_wrapper->set_align_items(AlignItems::Center);
modal_whole_page_wrapper->set_justify_content(JustifyContent::Center);
Element* modal = context.create_element<Element>(modal_whole_page_wrapper);
modal->set_display(Display::Flex);
modal->set_position(Position::Relative);
modal->set_flex(1.0f, 1.0f);
modal->set_flex_basis(100, Unit::Percent);
modal->set_flex_direction(FlexDirection::Column);
modal->set_width(100, Unit::Percent);
modal->set_max_width(700, Unit::Dp);
modal->set_height_auto();
modal->set_margin_auto();
modal->set_border_width(theme::border::width, Unit::Dp);
modal->set_border_radius(theme::border::radius_lg, Unit::Dp);
modal->set_border_color(theme::color::WhiteA20);
modal->set_background_color(theme::color::ModalOverlay);
context.create_element<Label>(modal, "Assign Players", LabelStyle::Large);
player_elements_wrapper = context.create_element<Element>(modal, 0, "div", false);
player_elements_wrapper->set_display(Display::Flex);
player_elements_wrapper->set_flex_direction(FlexDirection::Row);
player_elements_wrapper->set_justify_content(JustifyContent::SpaceBetween);
player_elements_wrapper->set_align_items(AlignItems::Center);
player_elements_wrapper->set_width(100, Unit::Percent);
player_elements_wrapper->set_padding(24, Unit::Dp);
}
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));
}
queue_update();
}
}
void AssignPlayersModal::create_player_elements() {
player_elements_wrapper->clear_children();
player_elements.clear();
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);
player_elements.push_back(player_element);
}
}
void AssignPlayersModal::open() {
if (!recompui::is_context_shown(assign_players_modal_context)) {
recompui::show_context(assign_players_modal_context, "");
}
is_open = true;
set_display(Display::Block);
create_player_elements();
queue_update();
}
void AssignPlayersModal::close() {
if (recompui::is_context_shown(assign_players_modal_context)) {
set_display(Display::None);
recompui::hide_context(assign_players_modal_context);
}
is_open = false;
}
recompui::AssignPlayersModal *assign_players_modal = nullptr;
void init_assign_players_modal() {
assign_players_modal_context = recompui::create_context();
assign_players_modal_context.open();
assign_players_modal = assign_players_modal_context.create_element<AssignPlayersModal>(assign_players_modal_context.get_root_element());
assign_players_modal_context.close();
}
} // namespace recompui
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "recomp_input.h"
#include "elements/ui_element.h"
namespace recompui {
class AssignPlayersModal : public Element {
protected:
bool is_open = false;
Element* player_elements_wrapper = nullptr;
std::vector<Element*> player_elements = {};
virtual void process_event(const Event &e) override;
std::string_view get_type_name() override { return "AssignPlayersModal"; }
private:
void create_player_elements();
public:
AssignPlayersModal(Element *parent);
virtual ~AssignPlayersModal();
void open();
void close();
};
extern AssignPlayersModal *assign_players_modal;
void init_assign_players_modal();
} // namespace recompui
+275
View File
@@ -0,0 +1,275 @@
#include "ui_config_page_controls.h"
#include "ui_assign_players_modal.h"
#include "elements/ui_button.h"
#include "elements/ui_label.h"
#include "elements/ui_toggle.h"
#include "elements/ui_container.h"
#include "elements/ui_binding_button.h"
namespace recompui {
const std::string_view active_state_style_name = "cont_opt_active";
GameInputRow::GameInputRow(
Element *parent,
GameInputContext *input_ctx,
BindingList bindings,
std::function<void()> on_hover_callback,
on_bind_click_callback on_bind_click
) : Element(parent, Events(EventType::Hover), "div", false) {
this->input_id = input_ctx->input_id;
this->on_hover_callback = on_hover_callback;
this->bindings = bindings;
set_display(Display::Flex);
set_position(Position::Relative);
set_flex_direction(FlexDirection::Row);
set_align_items(AlignItems::Center);
set_justify_content(JustifyContent::SpaceBetween);
set_width(100.0f, Unit::Percent);
set_height_auto();
set_padding_top(4.0f);
set_padding_right(16.0f);
set_padding_bottom(4.0f);
set_padding_left(20.0f);
set_border_radius(theme::border::radius_sm);
set_background_color(theme::color::Transparent);
active_style.set_background_color(theme::color::BGOverlay);
add_style(&active_style, active_state_style_name);
recompui::ContextId context = get_current_context();
auto label = context.create_element<Label>(this, input_ctx->name, LabelStyle::Normal);
label->set_flex_grow(2.0f);
label->set_flex_shrink(1.0f);
label->set_flex_basis(300.0f);
label->set_height_auto();
// TODO: whitespace nowrap impl
auto bindings_container = context.create_element<Element>(this, 0, "div", false);
{
bindings_container->set_display(Display::Flex);
bindings_container->set_position(Position::Relative);
bindings_container->set_flex_grow(2.0f);
bindings_container->set_flex_shrink(1.0f);
bindings_container->set_flex_basis(400.0f);
bindings_container->set_flex_direction(FlexDirection::Row);
bindings_container->set_align_items(AlignItems::Center);
bindings_container->set_justify_content(JustifyContent::SpaceBetween);
bindings_container->set_width(100.0f, Unit::Percent);
bindings_container->set_height(56.0f);
bindings_container->set_padding_right(12.0f);
bindings_container->set_padding_left(4.0f);
bindings_container->set_gap(4.0f);
for (size_t i = 0; i < bindings.size(); i++) {
BindingButton *binding_button = context.create_element<BindingButton>(bindings_container, "");
binding_button->set_binding(bindings[i].to_string());
binding_button->add_pressed_callback([this, i, on_bind_click]() {
on_bind_click(this->input_id, i);
});
binding_buttons.push_back(binding_button);
}
}
if (input_ctx->clearable) {
auto clear_button = context.create_element<IconButton>(this, "icons/Trash.svg", ButtonStyle::Danger, IconButtonSize::Large);
clear_button->add_pressed_callback([this]() {
// TODO: Add clear callback
});
} else {
auto reset_button = context.create_element<IconButton>(this, "icons/Reset.svg", ButtonStyle::Warning, IconButtonSize::Large);
reset_button->add_pressed_callback([this]() {
// TODO: Add reset callback
});
}
}
GameInputRow::~GameInputRow() {
}
void GameInputRow::update_bindings(BindingList &new_bindings) {
for (size_t i = 0; i < new_bindings.size(); i++) {
// skip update if no changes
if (
new_bindings[i].input_id == bindings[i].input_id &&
new_bindings[i].input_type == bindings[i].input_type) {
continue;
}
binding_buttons[i]->set_binding(new_bindings[i].to_string());
bindings[i] = new_bindings[i];
}
}
void GameInputRow::process_event(const Event &e) {
switch (e.type) {
case EventType::Hover:
{
bool hover_active = std::get<EventHover>(e.variant).active;
set_style_enabled(active_state_style_name, hover_active);
if (hover_active && on_hover_callback) {
on_hover_callback();
}
}
break;
default:
break;
}
}
ConfigPageControls::ConfigPageControls(
Element *parent,
int num_players,
std::vector<GameInputContext> game_input_contexts,
std::vector<PlayerBindings> game_input_bindings,
std::vector<bool> player_keyboard_enabled,
on_player_bind_callback on_player_bind,
set_player_keyboard_enabled_callback set_player_keyboard_enabled
) : ConfigPage(parent) {
this->on_player_bind = on_player_bind;
this->game_input_contexts = game_input_contexts;
this->num_players = num_players;
this->game_input_bindings = game_input_bindings;
this->player_keyboard_enabled = player_keyboard_enabled;
this->multiplayer_enabled = num_players > 1;
recompui::ContextId context = get_current_context();
add_header();
{
auto header_left = header->get_left();
for (uint8_t i = 0; i < num_players; i++) {
std::string player_text = "P" + std::to_string(i + 1);
auto player_button = context.create_element<PillButton>(header_left, player_text, "icons/Cont.svg", ButtonStyle::Basic, PillButtonSize::XLarge);
player_elements.push_back(player_button);
player_button->add_pressed_callback([this, i]() {
set_selected_player(i);
update_control_mappings();
});
}
}
{
auto header_right = header->get_right();
Button* assign_players_button = context.create_element<Button>(header_right, "Assign players", ButtonStyle::Primary);
assign_players_button->add_pressed_callback([]() {
recompui::assign_players_modal->open();
recompinput::start_player_assignment();
});
}
add_footer();
{
auto footer_left = footer->get_left();
keyboard_toggle = context.create_element<Toggle>(footer_left);
keyboard_toggle->set_checked(player_keyboard_enabled[selected_player]);
keyboard_toggle->add_checked_callback([this, set_player_keyboard_enabled](bool checked) {
set_player_keyboard_enabled(this->selected_player, checked);
update_control_mappings();
});
auto kb_label = context.create_element<Label>(footer_left, "Enable keyboard", LabelStyle::Normal);
kb_label->set_margin_left(12.0f);
}
{
auto footer_right = footer->get_right();
context.create_element<Button>(footer_right, "Reset to defaults", ButtonStyle::Warning);
}
description_container = context.create_element<Element>(body->get_right(), 0, "p", true);
description_container->set_text(
"Sometimes, the windows combine with the seams in a way\n"
"That twitches on a peak at the place where the spirit was slain\n"
"Hey, one foot leads to another\n"
"Night's for sleep, blue curtains, covers, sequins in the eyes\n"
"That's a fine time to dine\n"
"Divine who's circling, feeding the cards to the midwives"
);
set_selected_player(selected_player);
render_control_mappings();
}
void ConfigPageControls::render_control_mappings() {
recompui::ContextId context = get_current_context();
auto body_left = body->get_left();
body_left->clear_children();
body_left->set_display(Display::Block);
body_left->set_position(Position::Relative);
body_left->set_height(100.0f, Unit::Percent);
{
auto body_left_scroll = context.create_element<Element>(body_left, 0, "div", false);
body_left_scroll->set_display(Display::Block);
body_left_scroll->set_width(100.0f, Unit::Percent);
body_left_scroll->set_max_height(100.0f, Unit::Percent);
body_left_scroll->set_overflow_y(Overflow::Scroll);
for (int i = 0; i < game_input_contexts.size(); i++) {
auto &ctx = game_input_contexts[i];
context.create_element<GameInputRow>(
body_left_scroll,
&ctx,
game_input_bindings[selected_player].at(ctx.input_id),
[this, i]() {
this->on_option_hover(i);
},
[this](recompinput::GameInput game_input, int input_index) {
this->on_bind_click(game_input, input_index);
}
);
}
}
}
void ConfigPageControls::update_control_mappings() {
for (size_t i = 0; i < game_input_rows.size(); i++) {
game_input_rows[i]->update_bindings(
game_input_bindings[selected_player].at(game_input_contexts[i].input_id)
);
}
}
ConfigPageControls::~ConfigPageControls() {
}
void ConfigPageControls::on_bind_click(recompinput::GameInput game_input, int input_index) {
on_player_bind(this->selected_player, game_input, input_index);
}
void ConfigPageControls::set_selected_player(int player) {
static const std::array<theme::color, 8> player_colors = {
theme::color::Player1,
theme::color::Player2,
theme::color::Player3,
theme::color::Player4,
theme::color::Player5,
theme::color::Player6,
theme::color::Player7,
theme::color::Player8
};
selected_player = player;
for (uint8_t i = 0; i < num_players; i++) {
auto player_button = player_elements[i];
theme::color player_color = player_colors[i % player_colors.size()];
if (i == selected_player) {
player_button->apply_theme_style(player_color, false, true);
} else {
player_button->apply_theme_style(player_color, true, true);
}
}
keyboard_toggle->set_checked(player_keyboard_enabled[selected_player]);
}
void ConfigPageControls::on_option_hover(uint8_t index) {
if (description_container) {
description_container->set_text(game_input_contexts[index].description);
}
}
} // namespace recompui
+104
View File
@@ -0,0 +1,104 @@
#pragma once
#include "elements/ui_config_page.h"
#include "recomp_input.h"
#include "elements/ui_icon_button.h"
#include "elements/ui_binding_button.h"
#include "elements/ui_pill_button.h"
#include "elements/ui_toggle.h"
// TODO: remove after moving to recompinput
namespace recompinput {
using GameInput = recomp::GameInput;
using InputField = recomp::InputField;
using InputDevice = recomp::InputDevice;
constexpr int num_binding_slots = 4;
} // recompinput
namespace recompui {
struct GameInputContext {
std::string name;
std::string description;
recompinput::GameInput input_id;
bool clearable;
};
using BindingList = std::vector<recompinput::InputField>;
// Receives which GameInput to be bound to, and the index of the binding that was clicked
using on_bind_click_callback = std::function<void(recompinput::GameInput, int)>;
// Player index, GameInput to be bound to, and the index of the binding that is being assigned
using on_player_bind_callback = std::function<void(int, recompinput::GameInput, int)>;
// One single row of a game input mapping
class GameInputRow : public Element {
protected:
recompinput::GameInput input_id;
BindingList bindings;
int active_binding_index = -1;
bool is_binding = false;
std::vector<BindingButton*> binding_buttons = {};
Style active_style;
std::function<void()> on_hover_callback;
virtual void process_event(const Event &e) override;
std::string_view get_type_name() override { return "GameInputRow"; }
public:
GameInputRow(
Element *parent,
GameInputContext *input_ctx,
BindingList bindings,
std::function<void()> on_hover_callback,
on_bind_click_callback on_bind_click
);
virtual ~GameInputRow();
void update_bindings(BindingList &new_bindings);
};
using PlayerBindings = std::map<recompinput::GameInput, BindingList>;
// Sets if keyboard should be enabled, first arg player index, second is a bool to enable/disable
using set_player_keyboard_enabled_callback = std::function<void(int, bool)>;
class ConfigPageControls : public ConfigPage {
protected:
int selected_player = 0;
int num_players;
bool multiplayer_enabled;
std::vector<GameInputContext> game_input_contexts;
std::vector<PlayerBindings> game_input_bindings;
std::vector<bool> player_keyboard_enabled;
std::vector<PillButton*> player_elements;
std::vector<GameInputRow*> game_input_rows;
Toggle *keyboard_toggle;
Element *description_container = nullptr;
on_player_bind_callback on_player_bind;
std::string_view get_type_name() override { return "ConfigPageControls"; }
private:
void on_option_hover(uint8_t index);
void on_bind_click(recompinput::GameInput game_input, int input_index);
public:
ConfigPageControls(
Element *parent,
int num_players,
std::vector<GameInputContext> game_input_contexts,
std::vector<PlayerBindings> game_input_bindings,
std::vector<bool> player_keyboard_enabled,
on_player_bind_callback on_player_bind,
set_player_keyboard_enabled_callback set_player_keyboard_enabled
);
virtual ~ConfigPageControls();
void render_control_mappings();
void update_control_mappings();
void set_selected_player(int player);
};
} // namespace recompui
+113
View File
@@ -0,0 +1,113 @@
#include "ui_config_page_controls_element.h"
// TODO: remove hardcoded recompinput funcs and data
namespace recompinput {
}
namespace recompui {
ConfigPageControls *config_page;
static bool is_multiplayer_enabled() {
return true;
}
#define DEFINE_INPUT(name, value, readable) GameInputContext{ readable, "Description for " #readable " input.", recompinput::GameInput::##name, !(recompinput::GameInput::##name == recompinput::GameInput::TOGGLE_MENU || recompinput::GameInput::##name == recompinput::GameInput::ACCEPT_MENU) },
static std::vector<struct GameInputContext> temp_game_input_contexts = {
DEFINE_ALL_INPUTS()
};
#undef DEFINE_INPUT
struct PortConfig {
bool kb_enabled = false;
};
static std::vector<recompui::PlayerBindings> temp_local_player_bindings = {
recompui::PlayerBindings{},
recompui::PlayerBindings{},
recompui::PlayerBindings{},
recompui::PlayerBindings{},
recompui::PlayerBindings{},
recompui::PlayerBindings{},
recompui::PlayerBindings{},
recompui::PlayerBindings{},
};
static void temp_set_default_bindings(int index) {
#define DEFINE_INPUT(name, value, readable) temp_local_player_bindings[index][recompinput::GameInput::##name] = { recompinput::InputField{recomp::InputType::None, 0}, recompinput::InputField{recomp::InputType::None, 0}, recompinput::InputField{recomp::InputType::None, 0}, recompinput::InputField{recomp::InputType::None, 0}, };
DEFINE_ALL_INPUTS()
#undef DEFINE_INPUT
}
static void temp_set_all_defaults() {
for (size_t i = 0; i < temp_local_player_bindings.size(); i++) {
temp_set_default_bindings((int)i);
}
}
struct BindingInfo {
int player_index;
recompinput::GameInput game_input;
int binding_index;
bool is_scanning = false;
};
static BindingInfo temp_binding_info = { 0, recompinput::GameInput::COUNT, 0 };
static void temp_on_bind_player(int player_index, recompinput::GameInput game_input, int binding_index) {
temp_binding_info.player_index = player_index;
temp_binding_info.game_input = game_input;
temp_binding_info.binding_index = binding_index;
temp_binding_info.is_scanning = true;
}
static uint8_t get_num_players() {
return temp_local_player_bindings.size();
}
static std::vector<bool> temp_local_player_has_keyboard_enabled = {
false,
false,
false,
false,
false,
false,
false,
false,
};
static void temp_set_keyboard_player_enabled(uint8_t player, bool enabled) {
temp_local_player_has_keyboard_enabled[player] = enabled;
}
ElementConfigPageControls::ElementConfigPageControls(const Rml::String& tag) : Rml::Element(tag) {
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
SetProperty("width", "100%");
SetProperty("height", "100%");
recompui::Element this_compat(this);
recompui::ContextId context = get_current_context();
// TODO: remove temp stores
temp_set_all_defaults();
config_page = context.create_element<ConfigPageControls>(
&this_compat,
get_num_players(),
temp_game_input_contexts,
temp_local_player_bindings,
temp_local_player_has_keyboard_enabled,
temp_on_bind_player,
temp_set_keyboard_player_enabled
);
}
ElementConfigPageControls::~ElementConfigPageControls() {
}
} // namespace recompui
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "recomp_input.h"
#include "elements/ui_config_page.h"
#include "ui_config_page_controls.h"
namespace recompui {
extern ConfigPageControls *config_page;
class ElementConfigPageControls : public Rml::Element {
public:
ElementConfigPageControls(const Rml::String& tag);
virtual ~ElementConfigPageControls();
};
} // namespace recompui
+61
View File
@@ -0,0 +1,61 @@
#include "ui_config_page_example.h"
#include "elements/ui_button.h"
#include "elements/ui_icon_button.h"
#include "elements/ui_label.h"
namespace recompui {
ElementConfigPageExample::ElementConfigPageExample(const Rml::String& tag) : Rml::Element(tag) {
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
SetProperty("width", "100%");
SetProperty("height", "100%");
recompui::Element this_compat(this);
recompui::ContextId context = get_current_context();
auto config_page = context.create_element<ConfigPage>(&this_compat);
auto body = config_page->get_body();
{
auto body_left = body->get_left();
context.create_element<Label>(body_left, "First", LabelStyle::Normal);
context.create_element<Label>(body_left, "Second", LabelStyle::Normal);
context.create_element<Label>(body_left, "Third", LabelStyle::Normal);
context.create_element<Label>(body_left, "Fourth", LabelStyle::Normal);
context.create_element<Label>(body_left, "Fifth", LabelStyle::Normal);
context.create_element<Label>(body_left, "Sixth", LabelStyle::Normal);
}
{
auto body_right = body->get_right();
auto right_p = context.create_element<recompui::Element>(body_right, 0, "p", true);
right_p->set_text("Testing a really long string here that should probably wrap if its actually cool... but it might not be cool? im not sure. but we will need some extra formatting which im forgetting about right now so i guess it'll have to use set inner html or something like that idk.");
}
auto header = config_page->add_header();
{
auto header_left = header->get_left();
context.create_element<Button>(header_left, "Hello", ButtonStyle::Primary);
context.create_element<Button>(header_left, "Second button", ButtonStyle::Secondary);
}
{
auto header_right = header->get_right();
context.create_element<IconButton>(header_right, "icons/Arrow.svg", ButtonStyle::Tertiary, IconButtonSize::Small);
context.create_element<IconButton>(header_right, "icons/Reset.svg", ButtonStyle::Danger, IconButtonSize::XLarge);
}
auto footer = config_page->add_footer();
{
auto footer_left = footer->get_left();
context.create_element<Button>(footer_left, "Goodbye", ButtonStyle::Warning);
}
{
auto footer_right = footer->get_right();
context.create_element<Button>(footer_right, "Last button", ButtonStyle::Success);
context.create_element<IconButton>(footer_right, "icons/X.svg", ButtonStyle::Basic, IconButtonSize::Medium);
}
}
ElementConfigPageExample::~ElementConfigPageExample() {
}
} // namespace recompui
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "elements/ui_config_page.h"
namespace recompui {
class ElementConfigPageExample : public Rml::Element {
public:
ElementConfigPageExample(const Rml::String& tag);
virtual ~ElementConfigPageExample();
};
} // namespace recompui
+2
View File
@@ -10,6 +10,8 @@ struct RecompCustomElement {
static RecompCustomElement custom_elements[] = {
CUSTOM_ELEMENT("recomp-mod-menu", recompui::ElementModMenu),
CUSTOM_ELEMENT("recomp-config-sub-menu", recompui::ElementConfigSubMenu),
CUSTOM_ELEMENT("recomp-config-page-example", recompui::ElementConfigPageExample),
CUSTOM_ELEMENT("recomp-config-page-controls", recompui::ElementConfigPageControls),
};
void recompui::register_custom_elements() {
+2
View File
@@ -6,6 +6,8 @@
#include "ui_mod_menu.h"
#include "ui_config_sub_menu.h"
#include "ui_config_page_example.h"
#include "ui_config_page_controls_element.h"
namespace recompui {
void register_custom_elements();
+10 -8
View File
@@ -26,6 +26,7 @@
#include "ui_mod_menu.h"
#include "ui_mod_installer.h"
#include "ui_renderer.h"
#include "ui_assign_players_modal.h"
bool can_focus(Rml::Element* element) {
return element->GetOwnerDocument() != nullptr && element->GetProperty(Rml::PropertyId::TabIndex)->Get<Rml::Style::TabIndex>() != Rml::Style::TabIndex::None;
@@ -237,6 +238,7 @@ public:
launcher_menu_controller->load_document();
config_menu_controller->load_document();
recompui::init_prompt_context();
recompui::init_assign_players_modal();
}
void unload() {
@@ -469,8 +471,8 @@ int cont_button_to_key(SDL_ControllerButtonEvent& button) {
auto menuAcceptBinding0 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0, recomp::InputDevice::Controller);
auto menuAcceptBinding1 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1, recomp::InputDevice::Controller);
// note - magic number: 0 is InputType::None
if ((menuAcceptBinding0.input_type != 0 && button.button == menuAcceptBinding0.input_id) ||
(menuAcceptBinding1.input_type != 0 && button.button == menuAcceptBinding1.input_id)) {
if ((menuAcceptBinding0.input_type != recomp::InputType::None && button.button == menuAcceptBinding0.input_id) ||
(menuAcceptBinding1.input_type != recomp::InputType::None && button.button == menuAcceptBinding1.input_id)) {
return SDLK_RETURN;
}
@@ -478,8 +480,8 @@ int cont_button_to_key(SDL_ControllerButtonEvent& button) {
auto menuApplyBinding0 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller);
auto menuApplyBinding1 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller);
// note - magic number: 0 is InputType::None
if ((menuApplyBinding0.input_type != 0 && button.button == menuApplyBinding0.input_id) ||
(menuApplyBinding1.input_type != 0 && button.button == menuApplyBinding1.input_id)) {
if ((menuApplyBinding0.input_type != recomp::InputType::None && button.button == menuApplyBinding0.input_id) ||
(menuApplyBinding1.input_type != recomp::InputType::None && button.button == menuApplyBinding1.input_id)) {
return SDLK_f;
}
@@ -487,8 +489,8 @@ int cont_button_to_key(SDL_ControllerButtonEvent& button) {
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
// note - magic number: 0 is InputType::None
if ((menuToggleBinding0.input_type != 0 && button.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != 0 && button.button == menuToggleBinding1.input_id)) {
if ((menuToggleBinding0.input_type != recomp::InputType::None && button.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != recomp::InputType::None && button.button == menuToggleBinding1.input_id)) {
return SDLK_ESCAPE;
}
@@ -718,8 +720,8 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
// note - magic number: 0 is InputType::None
if ((menuToggleBinding0.input_type != 0 && cur_event.cbutton.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != 0 && cur_event.cbutton.button == menuToggleBinding1.input_id)) {
if ((menuToggleBinding0.input_type != recomp::InputType::None && cur_event.cbutton.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != recomp::InputType::None && cur_event.cbutton.button == menuToggleBinding1.input_id)) {
open_config = true;
}
break;