mirror of
https://github.com/BanjoRecomp/BanjoRecomp
synced 2026-06-04 02:16:10 -04:00
changes up to swapping to recompfrontend library
This commit is contained in:
+11
-1
@@ -169,8 +169,9 @@ set (SOURCES
|
||||
${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_color_element.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config_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_player_card.cpp
|
||||
@@ -186,6 +187,12 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_api_images.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_utils.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_page_options_menu.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_page_controls.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_tab_controls.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_tab_graphics.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_tab_manifest.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/config/ui_config_option.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_binding_button.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
|
||||
@@ -196,7 +203,9 @@ set (SOURCES
|
||||
${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_modal.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_pill_button.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_pseudo_border.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_select.cpp
|
||||
@@ -204,6 +213,7 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_span.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_svg.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_tab_set.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_text_input.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_theme.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.00008 11C9.00008 6.5 12.0002 4 16.0002 4C20.0002 4 23.0002 7 23.0002 11C23.0002 15 20.0002 18 16.0002 18" stroke="white" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M16 22V18" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="16" cy="28" r="2" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 424 B |
@@ -13,7 +13,9 @@ namespace banjo {
|
||||
// TODO: Move loading configs to the runtime once we have a way to allow per-project customization.
|
||||
void load_config();
|
||||
void save_config();
|
||||
|
||||
bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out);
|
||||
bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data);
|
||||
|
||||
void reset_input_bindings();
|
||||
void reset_cont_input_bindings(int profile_index);
|
||||
void reset_kb_input_bindings(int profile_index);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "common/rt64_user_configuration.h"
|
||||
#include "ultramodern/renderer_context.hpp"
|
||||
#include "librecomp/config.hpp"
|
||||
#include "librecomp/mods.hpp"
|
||||
|
||||
namespace RT64 {
|
||||
@@ -54,7 +55,7 @@ namespace banjo {
|
||||
|
||||
// Texture pack enable option. Must be an enum with two options.
|
||||
// The first option is treated as disabled and the second option is treated as enabled.
|
||||
bool is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors);
|
||||
bool is_texture_pack_enable_config_option(const recomp::config::ConfigOption& option, bool show_errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -45,7 +45,10 @@ namespace recomp {
|
||||
#define DEFINE_RECOMP_UI_INPUTS() \
|
||||
DEFINE_INPUT(TOGGLE_MENU, 0, "Toggle Menu") \
|
||||
DEFINE_INPUT(ACCEPT_MENU, 0, "Accept (Menu)") \
|
||||
DEFINE_INPUT(APPLY_MENU, 0, "Apply (Menu)")
|
||||
DEFINE_INPUT(BACK_MENU, 0, "Back (Menu)") \
|
||||
DEFINE_INPUT(APPLY_MENU, 0, "Apply (Menu)") \
|
||||
DEFINE_INPUT(TAB_LEFT_MENU, 0, "Tab Left (Menu)") \
|
||||
DEFINE_INPUT(TAB_RIGHT_MENU, 0, "Tab Right (Menu)")
|
||||
|
||||
#define DEFINE_ALL_INPUTS() \
|
||||
DEFINE_N64_BUTTON_INPUTS() \
|
||||
@@ -130,7 +133,10 @@ namespace recomp {
|
||||
|
||||
std::vector<InputField> toggle_menu;
|
||||
std::vector<InputField> accept_menu;
|
||||
std::vector<InputField> back_menu;
|
||||
std::vector<InputField> apply_menu;
|
||||
std::vector<InputField> tab_left_menu;
|
||||
std::vector<InputField> tab_right_menu;
|
||||
};
|
||||
|
||||
inline const std::vector<InputField>& get_default_mapping_for_input(const DefaultN64Mappings& defaults, const GameInput input) {
|
||||
@@ -156,7 +162,10 @@ namespace recomp {
|
||||
case GameInput::Y_AXIS_NEG: return defaults.analog_down;
|
||||
case GameInput::TOGGLE_MENU: return defaults.toggle_menu;
|
||||
case GameInput::ACCEPT_MENU: return defaults.accept_menu;
|
||||
case GameInput::BACK_MENU: return defaults.back_menu;
|
||||
case GameInput::APPLY_MENU: return defaults.apply_menu;
|
||||
case GameInput::TAB_LEFT_MENU: return defaults.tab_left_menu;
|
||||
case GameInput::TAB_RIGHT_MENU: return defaults.tab_right_menu;
|
||||
default: return empty_input_field;
|
||||
}
|
||||
}
|
||||
|
||||
+40
-3
@@ -10,11 +10,13 @@
|
||||
|
||||
#include "SDL.h"
|
||||
#include "RmlUi/Core.h"
|
||||
#include "recomp_input.h"
|
||||
|
||||
#include "../src/ui/util/hsv.h"
|
||||
#include "../src/ui/util/bem.h"
|
||||
#include "../src/ui/elements/ui_button.h"
|
||||
#include "../src/ui/elements/ui_theme.h"
|
||||
#include "../src/ui/elements/ui_types.h"
|
||||
|
||||
#include "../src/ui/core/ui_context.h"
|
||||
|
||||
@@ -61,7 +63,7 @@ namespace recompui {
|
||||
ContextId get_config_context_id();
|
||||
ContextId get_config_sub_menu_context_id();
|
||||
|
||||
enum class ConfigTab {
|
||||
enum class ConfigTabId {
|
||||
General,
|
||||
Controls,
|
||||
Graphics,
|
||||
@@ -70,8 +72,8 @@ namespace recompui {
|
||||
Debug,
|
||||
};
|
||||
|
||||
void set_config_tab(ConfigTab tab);
|
||||
int config_tab_to_index(ConfigTab tab);
|
||||
void set_config_tab(ConfigTabId tab);
|
||||
int config_tab_to_index(ConfigTabId tab);
|
||||
Rml::ElementTabSet* get_config_tabset();
|
||||
Rml::Element* get_mod_tab();
|
||||
void set_config_tabset_mod_nav();
|
||||
@@ -133,6 +135,41 @@ namespace recompui {
|
||||
void release_image(const std::string &src);
|
||||
|
||||
void drop_files(const std::list<std::filesystem::path> &file_list);
|
||||
void report_removed_element(Rml::Element* element);
|
||||
|
||||
namespace menu_action_mapping {
|
||||
struct key_map {
|
||||
// End result menu action
|
||||
const MenuAction action;
|
||||
// Mapped input from controller
|
||||
const recomp::GameInput input;
|
||||
// SDL key code from controller -> keyboard
|
||||
const int sdl;
|
||||
// RML key identifier passed to rmlui
|
||||
const Rml::Input::KeyIdentifier rml;
|
||||
};
|
||||
|
||||
static const key_map accept = { MenuAction::Accept, recomp::GameInput::ACCEPT_MENU, SDLK_RETURN, Rml::Input::KI_RETURN };
|
||||
static const key_map apply = { MenuAction::Apply, recomp::GameInput::APPLY_MENU, SDLK_f, Rml::Input::KI_F };
|
||||
static const key_map back = { MenuAction::Back, recomp::GameInput::BACK_MENU, SDLK_F15, Rml::Input::KI_F15 };
|
||||
static const key_map toggle = { MenuAction::Toggle, recomp::GameInput::TOGGLE_MENU, SDLK_ESCAPE, Rml::Input::KI_ESCAPE };
|
||||
static const key_map tab_left = { MenuAction::TabLeft, recomp::GameInput::TAB_LEFT_MENU, SDLK_F16, Rml::Input::KI_F16 };
|
||||
static const key_map tab_right = { MenuAction::TabRight, recomp::GameInput::TAB_RIGHT_MENU, SDLK_F17, Rml::Input::KI_F17 };
|
||||
|
||||
static const std::unordered_map<Rml::Input::KeyIdentifier, key_map> rml_key_to_action {
|
||||
{ accept.rml, accept },
|
||||
{ apply.rml, apply },
|
||||
{ back.rml, back },
|
||||
{ toggle.rml, toggle },
|
||||
{ tab_left.rml, tab_left },
|
||||
{ tab_right.rml, tab_right }
|
||||
};
|
||||
|
||||
MenuAction menu_action_from_rml_key(const Rml::Input::KeyIdentifier& key);
|
||||
};
|
||||
|
||||
// Constant that represents an input that doesn't have a promptfont icon associated with it.
|
||||
extern const std::string unknown_input;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
Submodule lib/N64ModernRuntime updated: c5e268aa0f...d46ed007a5
@@ -1,7 +1,7 @@
|
||||
#ifndef __UI_FUNCS_H__
|
||||
#define __UI_FUNCS_H__
|
||||
|
||||
// These two enums must be kept in sync with src/ui/elements/ui_types.h!
|
||||
// These three enums must be kept in sync with src/ui/elements/ui_types.h!
|
||||
typedef enum {
|
||||
UI_EVENT_NONE,
|
||||
UI_EVENT_CLICK,
|
||||
@@ -11,6 +11,9 @@ typedef enum {
|
||||
UI_EVENT_DRAG,
|
||||
UI_EVENT_RESERVED1, // Would be UI_EVENT_TEXT but text events aren't usable in mods currently
|
||||
UI_EVENT_UPDATE,
|
||||
UI_EVENT_NAVIGATE,
|
||||
UI_EVENT_MOUSE_BUTTON,
|
||||
UI_EVENT_MENU_ACTION,
|
||||
UI_EVENT_COUNT
|
||||
} RecompuiEventType;
|
||||
|
||||
@@ -21,6 +24,16 @@ typedef enum {
|
||||
UI_DRAG_END
|
||||
} RecompuiDragPhase;
|
||||
|
||||
typedef enum {
|
||||
UI_MENU_ACTION_NONE,
|
||||
UI_MENU_ACTION_ACCEPT,
|
||||
UI_MENU_ACTION_APPLY,
|
||||
UI_MENU_ACTION_BACK,
|
||||
UI_MENU_ACTION_TOGGLE,
|
||||
UI_MENU_ACTION_TAB_LEFT,
|
||||
UI_MENU_ACTION_TAB_RIGHT
|
||||
} RecompuiMenuAction;
|
||||
|
||||
typedef struct {
|
||||
RecompuiEventType type;
|
||||
union {
|
||||
@@ -46,6 +59,10 @@ typedef struct {
|
||||
float y;
|
||||
RecompuiDragPhase phase;
|
||||
} drag;
|
||||
|
||||
struct {
|
||||
RecompuiMenuAction action;
|
||||
} menu_action;
|
||||
} data;
|
||||
} RecompuiEventData;
|
||||
|
||||
|
||||
+13
-10
@@ -213,7 +213,7 @@ bool read_json(std::ifstream input_file, nlohmann::json& json_out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out) {
|
||||
bool banjo::read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out) {
|
||||
// Try reading and parsing the base file.
|
||||
if (read_json(std::ifstream{path}, json_out)) {
|
||||
return true;
|
||||
@@ -228,7 +228,7 @@ bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& j
|
||||
return false;
|
||||
}
|
||||
|
||||
bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data) {
|
||||
bool banjo::save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data) {
|
||||
{
|
||||
std::ofstream output_file = recomp::open_output_file_with_backup(path);
|
||||
if (!output_file.good()) {
|
||||
@@ -253,7 +253,7 @@ bool save_general_config(const std::filesystem::path& path) {
|
||||
config_json["analog_camera_invert_mode"] = banjo::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = banjo::get_debug_mode_enabled();
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
@@ -270,7 +270,7 @@ void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
|
||||
bool load_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -318,7 +318,10 @@ void assign_all_mappings(int profile_index, const recomp::DefaultN64Mappings& va
|
||||
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::ACCEPT_MENU, values.accept_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::BACK_MENU, values.back_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::APPLY_MENU, values.apply_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::TAB_LEFT_MENU, values.tab_left_menu);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::TAB_RIGHT_MENU, values.tab_right_menu);
|
||||
};
|
||||
|
||||
void banjo::reset_input_bindings() {
|
||||
@@ -365,12 +368,12 @@ void reset_graphics_options() {
|
||||
bool save_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
ultramodern::to_json(config_json, ultramodern::renderer::get_graphics_config());
|
||||
return save_json_with_backups(path, config_json);
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
bool load_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -420,7 +423,7 @@ bool save_controls_config(const std::filesystem::path& path) {
|
||||
controller["profile"] = recomp::get_input_profile_key(recomp::get_controller_profile_index(i));
|
||||
}
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
bool load_input_device_from_json(const nlohmann::json& config_json, int profile_index, recomp::InputDevice device, const std::string& key) {
|
||||
@@ -466,7 +469,7 @@ bool load_input_device_from_json(const nlohmann::json& config_json, int profile_
|
||||
|
||||
bool load_controls_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -526,12 +529,12 @@ bool save_sound_config(const std::filesystem::path& path) {
|
||||
config_json["main_volume"] = banjo::get_main_volume();
|
||||
config_json["bgm_volume"] = banjo::get_bgm_volume();
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
bool load_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -389,7 +389,10 @@ void clear_all_mappings(int profile_index) {
|
||||
|
||||
clear_mapping(profile_index, recomp::GameInput::TOGGLE_MENU);
|
||||
clear_mapping(profile_index, recomp::GameInput::ACCEPT_MENU);
|
||||
clear_mapping(profile_index, recomp::GameInput::BACK_MENU);
|
||||
clear_mapping(profile_index, recomp::GameInput::APPLY_MENU);
|
||||
clear_mapping(profile_index, recomp::GameInput::TAB_LEFT_MENU);
|
||||
clear_mapping(profile_index, recomp::GameInput::TAB_RIGHT_MENU);
|
||||
};
|
||||
|
||||
void recomp::initialize_input_bindings() {
|
||||
|
||||
+46
-14
@@ -601,8 +601,17 @@ const recomp::DefaultN64Mappings recomp::default_n64_keyboard_mappings = {
|
||||
.accept_menu = {
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN}
|
||||
},
|
||||
.back_menu = {
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F15}
|
||||
},
|
||||
.apply_menu = {
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F}
|
||||
},
|
||||
.tab_left_menu = {
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F16}
|
||||
},
|
||||
.tab_right_menu = {
|
||||
{.input_type = InputType::Keyboard, .input_id = SDL_SCANCODE_F17}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -671,9 +680,18 @@ const recomp::DefaultN64Mappings recomp::default_n64_controller_mappings = {
|
||||
.accept_menu = {
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH},
|
||||
},
|
||||
.apply_menu = {
|
||||
.back_menu = {
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST},
|
||||
},
|
||||
.apply_menu = {
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH},
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}
|
||||
},
|
||||
.tab_left_menu = {
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER}
|
||||
},
|
||||
.tab_right_menu = {
|
||||
{.input_type = InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -976,6 +994,8 @@ void recomp::set_single_controller_mode(bool single_controller) {
|
||||
InputState.single_controller = single_controller;
|
||||
}
|
||||
|
||||
const std::string recompui::unknown_input = "UNKNOWN";
|
||||
|
||||
std::string controller_button_to_string(SDL_GameControllerButton button) {
|
||||
switch (button) {
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_A:
|
||||
@@ -1008,20 +1028,20 @@ std::string controller_button_to_string(SDL_GameControllerButton button) {
|
||||
return PF_DPAD_LEFT;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
|
||||
return PF_DPAD_RIGHT;
|
||||
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1:
|
||||
// return "";
|
||||
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1:
|
||||
// return "";
|
||||
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2:
|
||||
// return "";
|
||||
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3:
|
||||
// return "";
|
||||
// case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4:
|
||||
// return "";
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1:
|
||||
return PF_STEAM_OPTIONS;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1:
|
||||
return PF_GAMEPAD_L4;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2:
|
||||
return PF_GAMEPAD_R4;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3:
|
||||
return PF_GAMEPAD_L5;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4:
|
||||
return PF_GAMEPAD_R5;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_TOUCHPAD:
|
||||
return PF_SONY_TOUCHPAD;
|
||||
default:
|
||||
return "Button " + std::to_string(button);
|
||||
return recompui::unknown_input;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1080,6 +1100,18 @@ std::unordered_map<SDL_Scancode, std::string> scancode_codepoints{
|
||||
{SDL_SCANCODE_F10, PF_KEYBOARD_F10},
|
||||
{SDL_SCANCODE_F11, PF_KEYBOARD_F11},
|
||||
{SDL_SCANCODE_F12, PF_KEYBOARD_F12},
|
||||
{SDL_SCANCODE_F13, "F13"},
|
||||
{SDL_SCANCODE_F14, "F14"},
|
||||
{SDL_SCANCODE_F15, "F15"},
|
||||
{SDL_SCANCODE_F16, "F16"},
|
||||
{SDL_SCANCODE_F17, "F17"},
|
||||
{SDL_SCANCODE_F18, "F18"},
|
||||
{SDL_SCANCODE_F19, "F19"},
|
||||
{SDL_SCANCODE_F20, "F20"},
|
||||
{SDL_SCANCODE_F21, "F21"},
|
||||
{SDL_SCANCODE_F22, "F22"},
|
||||
{SDL_SCANCODE_F23, "F23"},
|
||||
{SDL_SCANCODE_F24, "F24"},
|
||||
{SDL_SCANCODE_PRINTSCREEN, PF_KEYBOARD_PRINT_SCREEN},
|
||||
{SDL_SCANCODE_SCROLLLOCK, PF_KEYBOARD_SCROLL_LOCK},
|
||||
{SDL_SCANCODE_PAUSE, PF_KEYBOARD_PAUSE},
|
||||
@@ -1103,7 +1135,7 @@ std::string keyboard_input_to_string(SDL_Scancode key) {
|
||||
if (scancode_codepoints.find(key) != scancode_codepoints.end()) {
|
||||
return scancode_codepoints[key];
|
||||
}
|
||||
return std::to_string(key);
|
||||
return recompui::unknown_input;
|
||||
}
|
||||
|
||||
std::string controller_axis_to_string(int axis) {
|
||||
@@ -1138,6 +1170,6 @@ std::string recomp::InputField::to_string() const {
|
||||
case InputType::Keyboard:
|
||||
return keyboard_input_to_string((SDL_Scancode)input_id);
|
||||
default:
|
||||
return std::to_string((uint32_t)input_type) + "," + std::to_string(input_id);
|
||||
return recompui::unknown_input;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "banjo_render.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "concurrentqueue.h"
|
||||
#include "../ui/config/ui_config_tab_graphics.h"
|
||||
|
||||
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||
static bool sample_positions_supported = false;
|
||||
@@ -326,6 +327,8 @@ banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer:
|
||||
sample_positions_supported = false;
|
||||
}
|
||||
|
||||
recompui::update_msaa_supported(sample_positions_supported);
|
||||
|
||||
high_precision_fb_enabled = app->shaderLibrary->usesHDR;
|
||||
}
|
||||
|
||||
@@ -482,13 +485,13 @@ void banjo::renderer::enable_texture_pack(const recomp::mods::ModContext& contex
|
||||
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_id});
|
||||
|
||||
// Check for the texture pack enabled config option.
|
||||
const recomp::mods::ConfigSchema& config_schema = context.get_mod_config_schema(mod.manifest.mod_id);
|
||||
const recomp::config::ConfigSchema& config_schema = context.get_mod_config_schema(mod.manifest.mod_id);
|
||||
auto find_it = config_schema.options_by_id.find(banjo::renderer::special_option_texture_pack_enabled);
|
||||
if (find_it != config_schema.options_by_id.end()) {
|
||||
const recomp::mods::ConfigOption& config_option = config_schema.options[find_it->second];
|
||||
const recomp::config::ConfigOption& config_option = config_schema.options[find_it->second];
|
||||
|
||||
if (is_texture_pack_enable_config_option(config_option, false)) {
|
||||
recomp::mods::ConfigValueVariant value_variant = context.get_mod_config_value(mod.manifest.mod_id, config_option.id);
|
||||
recomp::config::ConfigValueVariant value_variant = context.get_mod_config_value(mod.manifest.mod_id, config_option.id);
|
||||
uint32_t value;
|
||||
if (uint32_t* value_ptr = std::get_if<uint32_t>(&value_variant)) {
|
||||
value = *value_ptr;
|
||||
@@ -522,16 +525,16 @@ void banjo::renderer::secondary_disable_texture_pack(const std::string& mod_id)
|
||||
|
||||
// HD texture enable option. Must be an enum with two options.
|
||||
// The first option is treated as disabled and the second option is treated as enabled.
|
||||
bool banjo::renderer::is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors) {
|
||||
bool banjo::renderer::is_texture_pack_enable_config_option(const recomp::config::ConfigOption& option, bool show_errors) {
|
||||
if (option.id == banjo::renderer::special_option_texture_pack_enabled) {
|
||||
if (option.type != recomp::mods::ConfigOptionType::Enum) {
|
||||
if (option.type != recomp::config::ConfigOptionType::Enum) {
|
||||
if (show_errors) {
|
||||
recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option is not an enum.").c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
|
||||
const recomp::config::ConfigOptionEnum &option_enum = std::get<recomp::config::ConfigOptionEnum>(option.variant);
|
||||
if (option_enum.options.size() != 2) {
|
||||
if (show_errors) {
|
||||
recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option doesn't have exactly 2 values.").c_str());
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include "librecomp/config.hpp"
|
||||
#include "librecomp/mods.hpp"
|
||||
|
||||
namespace recompui {
|
||||
// Callbacks for render updates relating to config option changes.
|
||||
using get_config_option_updates_callback = std::function<std::vector<recomp::config::ConfigOptionUpdateContext>()>;
|
||||
using clear_config_option_updates_callback = std::function<void()>;
|
||||
using check_page_dirty_callback = std::function<bool()>;
|
||||
|
||||
using set_option_value_callback = std::function<void(const std::string &option_id, recomp::config::ConfigValueVariant value)>;
|
||||
// Callbacks for various config option properties
|
||||
using get_option_value_callback = std::function<const recomp::config::ConfigValueVariant(const std::string &option_id)>;
|
||||
using get_option_name_callback = std::function<std::string(const std::string &option_id)>;
|
||||
using get_option_description_callback = std::function<std::string(const std::string &option_id)>;
|
||||
using get_option_disabled_callback = std::function<bool(const std::string &option_id)>;
|
||||
using get_option_hidden_callback = std::function<bool(const std::string &option_id)>;
|
||||
using get_option_configuration_callback = std::function<recomp::config::ConfigOptionVariant(const std::string &option_id)>;
|
||||
|
||||
// Text that displays on the right side of the options
|
||||
using get_enum_option_details_callback = std::function<std::string(const std::string &option_id)>;
|
||||
// Fetches if an individual enum option is disabled
|
||||
using get_enum_option_disabled_callback = std::function<bool(const std::string &option_id, uint32_t enum_index)>;
|
||||
|
||||
using on_option_hover_callback = std::function<void(const std::string &option_id)>;
|
||||
|
||||
class ConfigOptionPropertyCallbacks {
|
||||
// private:
|
||||
// const get_option_configuration_callback get_configuration;
|
||||
public:
|
||||
const get_option_configuration_callback get_configuration;
|
||||
const get_option_value_callback get_value;
|
||||
const get_option_name_callback get_name;
|
||||
const get_option_description_callback get_description;
|
||||
const get_option_hidden_callback get_hidden;
|
||||
const get_option_disabled_callback get_disabled = nullptr;
|
||||
|
||||
const get_enum_option_details_callback get_enum_option_details = nullptr;
|
||||
const get_enum_option_disabled_callback get_enum_option_disabled = nullptr;
|
||||
|
||||
ConfigOptionPropertyCallbacks(
|
||||
get_option_configuration_callback get_configuration,
|
||||
get_option_value_callback get_value,
|
||||
get_option_name_callback get_name,
|
||||
get_option_description_callback get_description,
|
||||
get_option_hidden_callback get_hidden,
|
||||
get_option_disabled_callback get_disabled = nullptr,
|
||||
get_enum_option_details_callback get_enum_option_details = nullptr,
|
||||
get_enum_option_disabled_callback get_enum_option_disabled = nullptr
|
||||
) :
|
||||
get_configuration(get_configuration),
|
||||
get_value(get_value),
|
||||
get_name(get_name),
|
||||
get_description(get_description),
|
||||
get_hidden(get_hidden),
|
||||
get_disabled(get_disabled),
|
||||
get_enum_option_details(get_enum_option_details),
|
||||
get_enum_option_disabled(get_enum_option_disabled) {}
|
||||
|
||||
ConfigOptionPropertyCallbacks(const ConfigOptionPropertyCallbacks&) = default;
|
||||
|
||||
recomp::config::ConfigOptionEnum get_enum_configuration(const std::string &option_id) {
|
||||
return std::get<recomp::config::ConfigOptionEnum>(get_configuration(option_id));
|
||||
}
|
||||
recomp::config::ConfigOptionNumber get_number_configuration(const std::string &option_id) {
|
||||
return std::get<recomp::config::ConfigOptionNumber>(get_configuration(option_id));
|
||||
}
|
||||
recomp::config::ConfigOptionString get_string_configuration(const std::string &option_id) {
|
||||
return std::get<recomp::config::ConfigOptionString>(get_configuration(option_id));
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,293 @@
|
||||
#include "ui_config_option.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
constexpr float config_option_element_margin_vertical = 12.0f;
|
||||
NovaConfigOptionElement::NovaConfigOptionElement(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
) :
|
||||
Element(parent, Events(EventType::Hover, EventType::Focus), "div", false),
|
||||
option_id(option_id),
|
||||
option_index(option_index),
|
||||
callbacks(callbacks),
|
||||
set_option_value(set_option_value),
|
||||
on_hover(on_hover)
|
||||
{
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_align_items(AlignItems::FlexStart);
|
||||
set_justify_content(JustifyContent::FlexStart);
|
||||
set_padding_top(config_option_element_margin_vertical);
|
||||
set_padding_left(12.0f);
|
||||
set_padding_right(12.0f);
|
||||
set_padding_bottom(config_option_element_margin_vertical);
|
||||
set_height_auto();
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_as_navigation_container(NavigationType::Auto);
|
||||
|
||||
ContextId context = recompui::get_current_context();
|
||||
auto name_text = callbacks->get_name(option_id);
|
||||
if (!name_text.empty()) {
|
||||
name_label = context.create_element<Label>(this, name_text, theme::Typography::LabelMD);
|
||||
name_label->set_margin_bottom(8.0f);
|
||||
} else {
|
||||
name_label = context.create_element<Label>(this, "", theme::Typography::LabelMD);
|
||||
set_padding_top(0.0f);
|
||||
}
|
||||
|
||||
update_hidden();
|
||||
}
|
||||
|
||||
void NovaConfigOptionElement::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Hover: {
|
||||
bool active = std::get<EventHover>(e.variant).active;
|
||||
if (active) {
|
||||
on_hover(option_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Focus: {
|
||||
bool active = std::get<EventFocus>(e.variant).active;
|
||||
if (active) {
|
||||
on_hover(option_id);
|
||||
scroll_into_view();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NovaConfigOptionElement::update_hidden() {
|
||||
if (callbacks->get_hidden(option_id)) {
|
||||
set_display(Display::None);
|
||||
set_enabled(false);
|
||||
} else {
|
||||
set_display(Display::Flex);
|
||||
set_enabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// NovaConfigOptionEnum
|
||||
NovaConfigOptionEnum::NovaConfigOptionEnum(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
) : NovaConfigOptionElement(parent, option_id, option_index, callbacks, set_option_value, on_hover)
|
||||
{
|
||||
ContextId context = recompui::get_current_context();
|
||||
Element *wrapper = context.create_element<Element>(this, 0, "div", false);
|
||||
wrapper->set_display(Display::Flex);
|
||||
wrapper->set_flex_direction(FlexDirection::Row);
|
||||
wrapper->set_align_items(AlignItems::Center);
|
||||
wrapper->set_justify_content(JustifyContent::FlexStart);
|
||||
wrapper->set_width(100.0f, Unit::Percent);
|
||||
wrapper->set_gap(12.0f);
|
||||
|
||||
// Negates the extra padding that radio tabs automatically add.
|
||||
name_label->set_margin_bottom(4.0f);
|
||||
|
||||
radio = context.create_element<Radio>(wrapper);
|
||||
radio->set_focus_callback([this](bool active) {
|
||||
if (active) {
|
||||
this->on_hover(this->option_id);
|
||||
}
|
||||
});
|
||||
radio->add_index_changed_callback([this](uint32_t index) {
|
||||
auto enum_opt = this->callbacks->get_enum_configuration(this->option_id);
|
||||
if (index < enum_opt.options.size()) {
|
||||
this->set_option_value(this->option_id, enum_opt.options[index].value);
|
||||
}
|
||||
});
|
||||
|
||||
details_label = context.create_element<Label>(wrapper, "", theme::Typography::LabelXS);
|
||||
details_label->set_color(theme::color::Primary);
|
||||
|
||||
auto enum_opt = callbacks->get_enum_configuration(option_id);
|
||||
for (uint32_t i = 0; i < enum_opt.options.size(); i++) {
|
||||
radio->add_option(enum_opt.options[i].name);
|
||||
}
|
||||
|
||||
update_value();
|
||||
update_enum_details();
|
||||
update_enum_disabled();
|
||||
// enable_focus();
|
||||
};
|
||||
|
||||
void NovaConfigOptionEnum::update_value() {
|
||||
recomp::config::ConfigValueVariant value_variant = callbacks->get_value(option_id);
|
||||
uint32_t option_value = std::get<uint32_t>(value_variant);
|
||||
|
||||
auto enum_opt = callbacks->get_enum_configuration(option_id);
|
||||
for (uint32_t i = 0; i < enum_opt.options.size(); i++) {
|
||||
if (enum_opt.options[i].value == option_value) {
|
||||
radio->set_index(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No matching option, set invalid index so no option is selected
|
||||
radio->set_index(enum_opt.options.size());
|
||||
};
|
||||
|
||||
void NovaConfigOptionEnum::update_disabled() {
|
||||
update_enum_disabled();
|
||||
};
|
||||
|
||||
void NovaConfigOptionEnum::update_enum_details() {
|
||||
const std::string &enum_details = callbacks->get_enum_option_details(option_id);
|
||||
if (enum_details.empty()) {
|
||||
details_label->set_text("");
|
||||
} else {
|
||||
details_label->set_text(enum_details);
|
||||
}
|
||||
};
|
||||
|
||||
void NovaConfigOptionEnum::update_enum_disabled() {
|
||||
bool all_disabled = callbacks->get_disabled(option_id);
|
||||
auto enum_opt = callbacks->get_enum_configuration(option_id);
|
||||
for (uint32_t i = 0; i < enum_opt.options.size(); i++) {
|
||||
bool enum_disabled = all_disabled || callbacks->get_enum_option_disabled(option_id, i);
|
||||
auto opt_element = radio->get_option_element(i);
|
||||
opt_element->set_enabled(!enum_disabled);
|
||||
}
|
||||
};
|
||||
|
||||
// NovaConfigOptionNumber
|
||||
NovaConfigOptionNumber::NovaConfigOptionNumber(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
) : NovaConfigOptionElement(parent, option_id, option_index, callbacks, set_option_value, on_hover)
|
||||
{
|
||||
ContextId context = recompui::get_current_context();
|
||||
auto num_opt = callbacks->get_number_configuration(option_id);
|
||||
|
||||
slider = context.create_element<Slider>(this, num_opt.percent ? SliderType::Percent : SliderType::Double);
|
||||
slider->set_width(100.0f, Unit::Percent);
|
||||
slider->set_max_width(100.0f, Unit::Percent);
|
||||
|
||||
slider->set_min_value(num_opt.min);
|
||||
slider->set_max_value(num_opt.max);
|
||||
slider->set_step_value(num_opt.step);
|
||||
slider->set_precision(num_opt.precision);
|
||||
slider->add_value_changed_callback([this](double v){
|
||||
this->set_option_value(this->option_id, v);
|
||||
});
|
||||
slider->set_focus_callback([this](bool active) {
|
||||
if (active) {
|
||||
this->on_hover(this->option_id);
|
||||
}
|
||||
});
|
||||
update_value();
|
||||
// enable_focus();
|
||||
update_disabled();
|
||||
};
|
||||
|
||||
void NovaConfigOptionNumber::update_value() {
|
||||
recomp::config::ConfigValueVariant value_variant = callbacks->get_value(option_id);
|
||||
double value = std::get<double>(value_variant);
|
||||
slider->set_value(value);
|
||||
};
|
||||
|
||||
void NovaConfigOptionNumber::update_disabled() {
|
||||
bool disabled = callbacks->get_disabled(option_id);
|
||||
set_enabled(!disabled);
|
||||
};
|
||||
|
||||
// NovaConfigOptionString
|
||||
NovaConfigOptionString::NovaConfigOptionString(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
) : NovaConfigOptionElement(parent, option_id, option_index, callbacks, set_option_value, on_hover)
|
||||
{
|
||||
// Negates the extra padding that text inputs automatically add.
|
||||
name_label->set_margin_bottom(4.0f);
|
||||
|
||||
ContextId context = recompui::get_current_context();
|
||||
text_input = context.create_element<TextInput>(this);
|
||||
text_input->set_width(100.0f, Unit::Percent);
|
||||
text_input->set_max_width(100.0f, Unit::Percent);
|
||||
update_value();
|
||||
text_input->add_text_changed_callback([this](const std::string &text){
|
||||
this->set_option_value(this->option_id, text);
|
||||
});
|
||||
text_input->set_focus_callback([this](bool active) {
|
||||
if (active) {
|
||||
this->on_hover(this->option_id);
|
||||
}
|
||||
});
|
||||
// enable_focus();
|
||||
};
|
||||
|
||||
void NovaConfigOptionString::update_value() {
|
||||
recomp::config::ConfigValueVariant value_variant = callbacks->get_value(option_id);
|
||||
std::string value = std::get<std::string>(value_variant);
|
||||
text_input->set_text(value);
|
||||
};
|
||||
|
||||
void NovaConfigOptionString::update_disabled() {
|
||||
bool disabled = callbacks->get_disabled(option_id);
|
||||
text_input->set_enabled(!disabled);
|
||||
};
|
||||
|
||||
// NovaConfigOptionBool
|
||||
NovaConfigOptionBool::NovaConfigOptionBool(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
) : NovaConfigOptionElement(parent, option_id, option_index, callbacks, set_option_value, on_hover)
|
||||
{
|
||||
// Negates the extra padding that text inputs automatically add.
|
||||
name_label->set_margin_bottom(4.0f);
|
||||
|
||||
ContextId context = recompui::get_current_context();
|
||||
toggle = context.create_element<Toggle>(this, ToggleSize::Medium);
|
||||
|
||||
update_value();
|
||||
toggle->add_checked_callback([this](bool checked){
|
||||
this->set_option_value(this->option_id, checked);
|
||||
});
|
||||
// toggle->set_focus_callback([this](bool active) {
|
||||
// if (active) {
|
||||
// this->on_hover(this->option_id);
|
||||
// }
|
||||
// });
|
||||
// enable_focus();
|
||||
};
|
||||
|
||||
void NovaConfigOptionBool::update_value() {
|
||||
recomp::config::ConfigValueVariant value_variant = callbacks->get_value(option_id);
|
||||
bool value = std::get<bool>(value_variant);
|
||||
toggle->set_checked(value);
|
||||
};
|
||||
|
||||
void NovaConfigOptionBool::update_disabled() {
|
||||
bool disabled = callbacks->get_disabled(option_id);
|
||||
toggle->set_enabled(!disabled);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_config_common.h"
|
||||
#include "../elements/ui_element.h"
|
||||
#include "../elements/ui_label.h"
|
||||
#include "../elements/ui_radio.h"
|
||||
#include "../elements/ui_text_input.h"
|
||||
#include "../elements/ui_slider.h"
|
||||
#include "../elements/ui_toggle.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class NovaConfigOptionElement : public Element {
|
||||
protected:
|
||||
std::string option_id;
|
||||
size_t option_index;
|
||||
ConfigOptionPropertyCallbacks *callbacks;
|
||||
on_option_hover_callback on_hover;
|
||||
set_option_value_callback set_option_value;
|
||||
Label *name_label = nullptr;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "NovaConfigOptionElement"; }
|
||||
|
||||
public:
|
||||
NovaConfigOptionElement(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
);
|
||||
virtual ~NovaConfigOptionElement() = default;
|
||||
// required overrides
|
||||
virtual void update_value() = 0;
|
||||
virtual void update_disabled() = 0;
|
||||
virtual Element *get_focus_element() = 0;
|
||||
void update_hidden();
|
||||
const std::string &get_option_id() { return option_id; }
|
||||
void set_nav_auto(NavDirection dir) override { get_focus_element()->set_nav_auto(dir); }
|
||||
void set_nav_none(NavDirection dir) override { get_focus_element()->set_nav_none(dir); }
|
||||
void set_nav(NavDirection dir, Element* element) override { get_focus_element()->set_nav(dir, element); }
|
||||
void set_nav_manual(NavDirection dir, const std::string& target) override { get_focus_element()->set_nav_manual(dir, target); }
|
||||
};
|
||||
|
||||
class NovaConfigOptionEnum : public NovaConfigOptionElement {
|
||||
protected:
|
||||
Radio *radio = nullptr;
|
||||
Label *details_label = nullptr;
|
||||
|
||||
std::string_view get_type_name() override { return "NovaConfigOptionEnum"; }
|
||||
public:
|
||||
NovaConfigOptionEnum(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
);
|
||||
|
||||
Element* get_focus_element() override { return radio; }
|
||||
void update_value() override;
|
||||
void update_disabled() override;
|
||||
|
||||
void update_enum_details();
|
||||
void update_enum_disabled();
|
||||
};
|
||||
|
||||
class NovaConfigOptionNumber : public NovaConfigOptionElement {
|
||||
protected:
|
||||
Slider *slider = nullptr;
|
||||
|
||||
std::string_view get_type_name() override { return "NovaConfigOptionNumber"; }
|
||||
public:
|
||||
NovaConfigOptionNumber(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
);
|
||||
|
||||
Element* get_focus_element() override { return slider; }
|
||||
void update_value() override;
|
||||
void update_disabled() override;
|
||||
};
|
||||
|
||||
class NovaConfigOptionString : public NovaConfigOptionElement {
|
||||
protected:
|
||||
TextInput *text_input = nullptr;
|
||||
|
||||
std::string_view get_type_name() override { return "NovaConfigOptionString"; }
|
||||
public:
|
||||
NovaConfigOptionString(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
);
|
||||
|
||||
Element* get_focus_element() override { return text_input; }
|
||||
void update_value() override;
|
||||
void update_disabled() override;
|
||||
};
|
||||
|
||||
class NovaConfigOptionBool : public NovaConfigOptionElement {
|
||||
protected:
|
||||
Toggle *toggle = nullptr;
|
||||
|
||||
std::string_view get_type_name() override { return "NovaConfigOptionBool"; }
|
||||
public:
|
||||
NovaConfigOptionBool(
|
||||
Element *parent,
|
||||
std::string option_id,
|
||||
size_t option_index,
|
||||
ConfigOptionPropertyCallbacks *callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
on_option_hover_callback on_hover
|
||||
);
|
||||
|
||||
Element* get_focus_element() override { return toggle; }
|
||||
void update_value() override;
|
||||
void update_disabled() override;
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,14 +1,16 @@
|
||||
#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"
|
||||
#include "elements/ui_select.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"
|
||||
#include "../elements/ui_select.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ConfigPageControls *controls_page = nullptr;
|
||||
|
||||
const std::string_view active_state_style_name = "cont_opt_active";
|
||||
|
||||
GameInputRow::GameInputRow(
|
||||
@@ -129,12 +131,17 @@ void GameInputRow::process_event(const Event &e) {
|
||||
ConfigPageControls::ConfigPageControls(
|
||||
Element *parent,
|
||||
int num_players,
|
||||
std::vector<GameInputContext> game_input_contexts
|
||||
std::vector<GameInputContext> game_input_contexts,
|
||||
Element *nav_up_element,
|
||||
set_first_focusable_below_tabs_t set_first_focusable_below_tabs
|
||||
) : ConfigPage(parent) {
|
||||
controls_page = this;
|
||||
this->game_input_contexts = game_input_contexts;
|
||||
this->num_players = num_players;
|
||||
this->multiplayer_enabled = num_players > 1;
|
||||
this->nav_up_element = nav_up_element;
|
||||
this->set_first_navigation_element_cb = set_first_focusable_below_tabs;
|
||||
|
||||
multiplayer_enabled = this->num_players > 1;
|
||||
multiplayer_view_mappings = !multiplayer_enabled;
|
||||
|
||||
set_selected_player(selected_player);
|
||||
@@ -167,16 +174,19 @@ void ConfigPageControls::force_update() {
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_all() {
|
||||
render_header();
|
||||
render_body();
|
||||
set_first_navigation_element(nullptr);
|
||||
Element *header_focusable = render_header();
|
||||
render_body(header_focusable);
|
||||
render_footer();
|
||||
confirm_first_navigation_element();
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_header() {
|
||||
Element *ConfigPageControls::render_header() {
|
||||
if (!multiplayer_enabled) {
|
||||
hide_header();
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
Element *header_focusable = nullptr;
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
add_header();
|
||||
@@ -207,36 +217,62 @@ void ConfigPageControls::render_header() {
|
||||
this->multiplayer_view_mappings = false;
|
||||
this->force_update();
|
||||
});
|
||||
set_first_navigation_element(go_back_button);
|
||||
if (nav_up_element) {
|
||||
go_back_button->set_nav(NavDirection::Up, nav_up_element);
|
||||
}
|
||||
header_focusable = go_back_button;
|
||||
} else {
|
||||
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();
|
||||
});
|
||||
set_first_navigation_element(assign_players_button);
|
||||
if (nav_up_element) {
|
||||
assign_players_button->set_nav(NavDirection::Up, nav_up_element);
|
||||
}
|
||||
header_focusable = assign_players_button;
|
||||
}
|
||||
}
|
||||
|
||||
return first_nav_element;
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_body() {
|
||||
void ConfigPageControls::render_body(Element *header_focusable) {
|
||||
bool show_mappings = (multiplayer_enabled && multiplayer_view_mappings) || !multiplayer_enabled;
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
if (show_mappings) {
|
||||
body->get_right()->set_display(Display::Flex);
|
||||
render_body_mappings();
|
||||
render_body_mappings(header_focusable);
|
||||
} else {
|
||||
body->get_right()->set_display(Display::None);
|
||||
render_body_players();
|
||||
render_body_players(header_focusable);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_body_mappings() {
|
||||
void ConfigPageControls::set_first_navigation_element(Element *element) {
|
||||
// Only reset the first nav element or set it once
|
||||
if (element == nullptr || first_nav_element == nullptr) {
|
||||
first_nav_element = element;
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::confirm_first_navigation_element() {
|
||||
if (set_first_navigation_element_cb) {
|
||||
set_first_navigation_element_cb(first_nav_element);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_body_mappings(Element *header_focusable) {
|
||||
recompui::ContextId context = get_current_context();
|
||||
body->set_as_navigation_container(NavigationType::Horizontal);
|
||||
|
||||
// left side
|
||||
{
|
||||
render_control_mappings();
|
||||
render_control_mappings(header_focusable);
|
||||
}
|
||||
|
||||
// right side
|
||||
@@ -254,13 +290,15 @@ void ConfigPageControls::render_body_mappings() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_body_players() {
|
||||
void ConfigPageControls::render_body_players(Element *header_focusable) {
|
||||
recompui::ContextId context = get_current_context();
|
||||
body->set_as_navigation_container(NavigationType::Horizontal);
|
||||
|
||||
auto body_left = body->get_left();
|
||||
body_left->clear_children();
|
||||
|
||||
auto player_grid = context.create_element<Element>(body_left, 0, "div", false);
|
||||
player_grid->set_as_navigation_container(NavigationType::Auto);
|
||||
player_grid->set_display(Display::Flex);
|
||||
player_grid->set_flex_direction(FlexDirection::Row);
|
||||
player_grid->set_flex_wrap(FlexWrap::Wrap);
|
||||
@@ -283,6 +321,17 @@ void ConfigPageControls::render_body_players() {
|
||||
this->on_edit_player_profile(player_index);
|
||||
});
|
||||
player_cards.push_back(player_card);
|
||||
if (i == 0) {
|
||||
player_card->set_as_primary_focus(true);
|
||||
// if (header_focusable) {
|
||||
// player_card->set_nav(NavDirection::Up, header_focusable);
|
||||
// } else {
|
||||
// if (nav_up_element) {
|
||||
// player_card->set_nav(NavDirection::Up, nav_up_element);
|
||||
// }
|
||||
// set_first_navigation_element(player_card);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +362,7 @@ void ConfigPageControls::render_footer() {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
add_footer();
|
||||
footer->set_as_navigation_container(NavigationType::Horizontal);
|
||||
{
|
||||
auto footer_left = footer->get_left();
|
||||
footer_left->clear_children();
|
||||
@@ -338,8 +388,9 @@ void ConfigPageControls::render_footer() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_control_mappings() {
|
||||
void ConfigPageControls::render_control_mappings(Element *header_focusable) {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
auto body_left = body->get_left();
|
||||
body_left->clear_children();
|
||||
|
||||
@@ -353,6 +404,7 @@ void ConfigPageControls::render_control_mappings() {
|
||||
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);
|
||||
body_left_scroll->set_as_navigation_container(NavigationType::GridCol);
|
||||
|
||||
game_input_rows.clear();
|
||||
for (int i = 0; i < game_input_contexts.size(); i++) {
|
||||
@@ -371,6 +423,19 @@ void ConfigPageControls::render_control_mappings() {
|
||||
}
|
||||
);
|
||||
game_input_rows.push_back(row);
|
||||
row->set_as_navigation_container(NavigationType::GridRow);
|
||||
|
||||
// if (i == 0) {
|
||||
// if (header_focusable) {
|
||||
// row->set_nav(NavDirection::Up, header_focusable);
|
||||
// } else {
|
||||
// // Something above the whole page
|
||||
// if (nav_up_element) {
|
||||
// row->set_nav(NavDirection::Up, nav_up_element);
|
||||
// }
|
||||
// set_first_navigation_element(row);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
update_control_mappings();
|
||||
@@ -404,6 +469,7 @@ void ConfigPageControls::update_control_mappings() {
|
||||
}
|
||||
|
||||
ConfigPageControls::~ConfigPageControls() {
|
||||
controls_page = nullptr;
|
||||
}
|
||||
|
||||
recompinput::InputDevice ConfigPageControls::get_player_input_device() {
|
||||
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "elements/ui_config_page.h"
|
||||
#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"
|
||||
#include "ui_player_card.h"
|
||||
#include "../elements/ui_icon_button.h"
|
||||
#include "../elements/ui_binding_button.h"
|
||||
#include "../elements/ui_pill_button.h"
|
||||
#include "../elements/ui_toggle.h"
|
||||
#include "../elements/ui_modal.h"
|
||||
#include "../ui_player_card.h"
|
||||
|
||||
// TODO: remove after moving to recompinput
|
||||
namespace recompinput {
|
||||
@@ -90,6 +91,9 @@ protected:
|
||||
Toggle *keyboard_toggle;
|
||||
Element *description_container = nullptr;
|
||||
on_player_bind_callback on_player_bind;
|
||||
Element *nav_up_element = nullptr;
|
||||
Element *first_nav_element = nullptr;
|
||||
set_first_focusable_below_tabs_t set_first_navigation_element_cb = nullptr;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "ConfigPageControls"; }
|
||||
@@ -105,25 +109,32 @@ private:
|
||||
void on_edit_player_profile(int player_index);
|
||||
|
||||
void render_all();
|
||||
void render_header();
|
||||
void render_body();
|
||||
void render_body_players();
|
||||
void render_body_mappings();
|
||||
// returns pointer to primary focusable element in header or nullptr if not rendered
|
||||
Element *render_header();
|
||||
void render_body(Element *header_focusable);
|
||||
void render_body_players(Element *header_focusable);
|
||||
void render_body_mappings(Element *header_focusable);
|
||||
void render_footer();
|
||||
void set_first_navigation_element(Element *element);
|
||||
void confirm_first_navigation_element();
|
||||
|
||||
recompinput::InputDevice get_player_input_device();
|
||||
public:
|
||||
ConfigPageControls(
|
||||
Element *parent,
|
||||
int num_players,
|
||||
std::vector<GameInputContext> game_input_contexts
|
||||
std::vector<GameInputContext> game_input_contexts,
|
||||
Element *nav_up_element = nullptr,
|
||||
set_first_focusable_below_tabs_t set_first_focusable_below_tabs = nullptr
|
||||
);
|
||||
virtual ~ConfigPageControls();
|
||||
|
||||
void force_update();
|
||||
void render_control_mappings();
|
||||
void render_control_mappings(Element *header_focusable);
|
||||
void update_control_mappings();
|
||||
void set_selected_player(int player);
|
||||
};
|
||||
|
||||
extern ConfigPageControls *controls_page;
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,307 @@
|
||||
#include "ui_config_page_options_menu.h"
|
||||
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ConfigPageOptionsMenu::ConfigPageOptionsMenu(
|
||||
Element *parent,
|
||||
recomp::config::Config *config,
|
||||
const ConfigOptionPropertyCallbacks &callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
bool requires_confirmation,
|
||||
bool is_internal
|
||||
) : ConfigPage(parent, Events(EventType::Hover, EventType::Update, EventType::MenuAction)),
|
||||
config(config),
|
||||
callbacks(ConfigOptionPropertyCallbacks(callbacks)),
|
||||
set_option_value(set_option_value),
|
||||
requires_confirmation(requires_confirmation),
|
||||
is_internal(is_internal)
|
||||
{
|
||||
ContextId context = recompui::get_current_context();
|
||||
set_as_navigation_container(NavigationType::Vertical);
|
||||
|
||||
render_config_options();
|
||||
description_container = context.create_element<Element>(body->get_right(), 0, "p", true);
|
||||
description_container->set_typography(theme::Typography::Body);
|
||||
description_container->set_line_height(28.0f);
|
||||
description_container->set_padding(8.0f);
|
||||
set_description_text("");
|
||||
|
||||
if (requires_confirmation) {
|
||||
add_footer();
|
||||
render_confirmation_footer();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::set_description_text(const std::string &text) {
|
||||
if (description_container) {
|
||||
if (is_internal) {
|
||||
description_container->set_text_unsafe(text);
|
||||
} else {
|
||||
description_container->set_text(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NovaConfigOptionElement* ConfigPageOptionsMenu::get_element_from_option_id(const std::string &option_id) {
|
||||
for (auto *element : config_option_elements) {
|
||||
if (element->get_option_id() == option_id) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::perform_option_render_updates() {
|
||||
using ConfigOptionUpdateType = recomp::config::ConfigOptionUpdateType;
|
||||
if (get_config_option_updates == nullptr || clear_config_option_updates == nullptr) {
|
||||
return;
|
||||
}
|
||||
queue_update();
|
||||
|
||||
auto options_updates = get_config_option_updates();
|
||||
bool has_updates = !options_updates.empty();
|
||||
for (auto &option_update : options_updates) {
|
||||
size_t option_index = option_update.option_index;
|
||||
auto schema = config->get_config_schema();
|
||||
std::string &option_id = schema.options[option_index].id;
|
||||
NovaConfigOptionElement* element = get_element_from_option_id(option_id);
|
||||
if (element == nullptr) {
|
||||
printf("Failed to update conf option: '%s'\n", option_id.c_str());
|
||||
continue;
|
||||
}
|
||||
for (auto &update_type :option_update.updates) {
|
||||
switch (update_type) {
|
||||
case ConfigOptionUpdateType::Disabled: {
|
||||
element->update_disabled();
|
||||
break;
|
||||
}
|
||||
case ConfigOptionUpdateType::Hidden: {
|
||||
element->update_hidden();
|
||||
break;
|
||||
}
|
||||
case ConfigOptionUpdateType::EnumDetails: {
|
||||
NovaConfigOptionEnum *enum_element = static_cast<NovaConfigOptionEnum*>(element);
|
||||
enum_element->update_enum_details();
|
||||
break;
|
||||
}
|
||||
case ConfigOptionUpdateType::EnumDisabled: {
|
||||
NovaConfigOptionEnum *enum_element = static_cast<NovaConfigOptionEnum*>(element);
|
||||
enum_element->update_enum_disabled();
|
||||
break;
|
||||
}
|
||||
case ConfigOptionUpdateType::Value: {
|
||||
element->update_value();
|
||||
break;
|
||||
}
|
||||
case ConfigOptionUpdateType::Description: {
|
||||
if (description_container && description_option_id == option_id) {
|
||||
set_description_text(callbacks.get_description(option_id));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clear_config_option_updates();
|
||||
if (has_updates) {
|
||||
// apply_option_navigation();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Hover: {
|
||||
bool active = std::get<EventHover>(e.variant).active;
|
||||
if (!active) {
|
||||
set_description_text("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Update: {
|
||||
perform_option_render_updates();
|
||||
break;
|
||||
}
|
||||
case EventType::MenuAction: {
|
||||
auto action = std::get<EventMenuAction>(e.variant).action;
|
||||
if (action == MenuAction::Apply) {
|
||||
if (on_apply_callback != nullptr) {
|
||||
on_apply_callback();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::render_confirmation_footer() {
|
||||
ContextId context = recompui::get_current_context();
|
||||
footer->set_as_navigation_container(NavigationType::Horizontal);
|
||||
|
||||
apply_button = context.create_element<Button>(footer->get_right(), "Apply", ButtonStyle::Secondary);
|
||||
apply_button->set_enabled(false);
|
||||
apply_button->set_as_primary_focus();
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::on_set_option_value(const std::string &option_id, recomp::config::ConfigValueVariant value) {
|
||||
set_option_value(option_id, value);
|
||||
if (check_page_dirty && apply_button != nullptr) {
|
||||
apply_button->set_enabled(check_page_dirty());
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::render_config_options() {
|
||||
ContextId context = recompui::get_current_context();
|
||||
Element *body_left = get_body()->get_left();
|
||||
body_left->clear_children();
|
||||
body_left->set_padding(0.0f);
|
||||
|
||||
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_min_height(100.0f, Unit::Percent);
|
||||
body_left_scroll->set_max_height(100.0f, Unit::Percent);
|
||||
body_left_scroll->set_padding(16.0f);
|
||||
body_left_scroll->set_overflow_y(Overflow::Auto);
|
||||
|
||||
config_option_elements.clear();
|
||||
bound_on_option_hover = [this](const std::string &option_id) {
|
||||
this->on_option_hover(option_id);
|
||||
};
|
||||
bound_set_option_value = [this](const std::string &option_id, recomp::config::ConfigValueVariant value) {
|
||||
this->on_set_option_value(option_id, value);
|
||||
};
|
||||
auto schema = config->get_config_schema();
|
||||
for (size_t i = 0; i < schema.options.size(); i++) {
|
||||
auto &config_option = schema.options[i];
|
||||
NovaConfigOptionElement *element = nullptr;
|
||||
switch (config_option.type) {
|
||||
case recomp::config::ConfigOptionType::Enum: {
|
||||
element = context.create_element<NovaConfigOptionEnum>(
|
||||
body_left_scroll,
|
||||
config_option.id,
|
||||
i,
|
||||
&callbacks,
|
||||
bound_set_option_value,
|
||||
bound_on_option_hover
|
||||
);
|
||||
break;
|
||||
}
|
||||
case recomp::config::ConfigOptionType::Number: {
|
||||
element = context.create_element<NovaConfigOptionNumber>(
|
||||
body_left_scroll,
|
||||
config_option.id,
|
||||
i,
|
||||
&callbacks,
|
||||
bound_set_option_value,
|
||||
bound_on_option_hover
|
||||
);
|
||||
break;
|
||||
}
|
||||
case recomp::config::ConfigOptionType::String: {
|
||||
element = context.create_element<NovaConfigOptionString>(
|
||||
body_left_scroll,
|
||||
config_option.id,
|
||||
i,
|
||||
&callbacks,
|
||||
bound_set_option_value,
|
||||
bound_on_option_hover
|
||||
);
|
||||
break;
|
||||
}
|
||||
case recomp::config::ConfigOptionType::Bool: {
|
||||
element = context.create_element<NovaConfigOptionBool>(
|
||||
body_left_scroll,
|
||||
config_option.id,
|
||||
i,
|
||||
&callbacks,
|
||||
bound_set_option_value,
|
||||
bound_on_option_hover
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assert(false && "Unknown config option type");
|
||||
}
|
||||
};
|
||||
element->update_disabled();
|
||||
element->update_hidden();
|
||||
config_option_elements.push_back(element);
|
||||
}
|
||||
|
||||
apply_option_navigation();
|
||||
}
|
||||
}
|
||||
|
||||
Element* ConfigPageOptionsMenu::get_navigation_element(int cur_index, int direction) {
|
||||
int new_index = cur_index + direction;
|
||||
if (new_index < 0) {
|
||||
// need to handle getting above element
|
||||
return nullptr;
|
||||
}
|
||||
auto schema = config->get_config_schema();
|
||||
if (
|
||||
new_index >= static_cast<int>(config_option_elements.size()) ||
|
||||
new_index >= static_cast<int>(schema.options.size())) {
|
||||
// need to handle getting below element
|
||||
return nullptr;
|
||||
}
|
||||
auto option = schema.options[new_index];
|
||||
if (callbacks.get_disabled(option.id) || callbacks.get_hidden(option.id)) {
|
||||
return get_navigation_element(new_index, direction);
|
||||
}
|
||||
|
||||
return config_option_elements[new_index]->get_focus_element();
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::apply_option_navigation() {
|
||||
Element *first_enabled = nullptr;
|
||||
auto schema = config->get_config_schema();
|
||||
for (size_t i = 0; i < schema.options.size(); i++) {
|
||||
auto option = schema.options[i];
|
||||
bool this_is_fully_enabled = !callbacks.get_disabled(option.id) && !callbacks.get_hidden(option.id);
|
||||
if (first_enabled == nullptr && this_is_fully_enabled) {
|
||||
first_enabled = config_option_elements[i];
|
||||
config_option_elements[i]->set_as_primary_focus(true);
|
||||
} else {
|
||||
config_option_elements[i]->set_as_primary_focus(false);
|
||||
}
|
||||
|
||||
auto *prev_element = get_navigation_element(i, -1);
|
||||
if (prev_element != nullptr) {
|
||||
config_option_elements[i]->set_nav(NavDirection::Up, prev_element);
|
||||
}
|
||||
auto *next_element = get_navigation_element(i, 1);
|
||||
if (next_element != nullptr) {
|
||||
config_option_elements[i]->set_nav(NavDirection::Down, next_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::on_option_hover(const std::string &option_id) {
|
||||
if (description_container) {
|
||||
description_option_id = option_id;
|
||||
set_description_text(callbacks.get_description(option_id));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageOptionsMenu::set_on_apply_callback(std::function<void()> callback) {
|
||||
if (apply_button) {
|
||||
on_apply_callback = [this, callback]{
|
||||
callback();
|
||||
if (this->apply_button && this->check_page_dirty) {
|
||||
this->apply_button->set_enabled(this->check_page_dirty());
|
||||
}
|
||||
};
|
||||
apply_button->add_pressed_callback(on_apply_callback);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_config_common.h"
|
||||
#include "ui_config_option.h"
|
||||
#include "../elements/ui_config_page.h"
|
||||
#include "../elements/ui_modal.h"
|
||||
#include "../elements/ui_button.h"
|
||||
#include "librecomp/config.hpp"
|
||||
#include "librecomp/mods.hpp"
|
||||
|
||||
namespace recompui {
|
||||
class ConfigPageOptionsMenu : public ConfigPage {
|
||||
protected:
|
||||
recomp::config::Config *config;
|
||||
ConfigOptionPropertyCallbacks callbacks;
|
||||
set_option_value_callback set_option_value;
|
||||
std::function<void()> on_apply_callback;
|
||||
bool requires_confirmation = false;
|
||||
bool is_internal = false;
|
||||
|
||||
check_page_dirty_callback check_page_dirty = nullptr;
|
||||
get_config_option_updates_callback get_config_option_updates = nullptr;
|
||||
clear_config_option_updates_callback clear_config_option_updates = nullptr;
|
||||
|
||||
std::vector<NovaConfigOptionElement*> config_option_elements;
|
||||
Element *description_container = nullptr;
|
||||
std::string description_option_id = "";
|
||||
Button *apply_button = nullptr;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "ConfigPageOptionsMenu"; }
|
||||
private:
|
||||
on_option_hover_callback bound_on_option_hover;
|
||||
set_option_value_callback bound_set_option_value;
|
||||
|
||||
void render_config_options();
|
||||
void render_confirmation_footer();
|
||||
void on_option_hover(const std::string &option_id);
|
||||
void on_set_option_value(const std::string &option_id, recomp::config::ConfigValueVariant value);
|
||||
NovaConfigOptionElement* get_element_from_option_id(const std::string &option_id);
|
||||
void perform_option_render_updates();
|
||||
void apply_option_navigation();
|
||||
Element* get_navigation_element(int cur_index, int direction);
|
||||
void set_description_text(const std::string &text);
|
||||
public:
|
||||
ConfigPageOptionsMenu(
|
||||
Element *parent,
|
||||
recomp::config::Config *config,
|
||||
const ConfigOptionPropertyCallbacks &callbacks,
|
||||
set_option_value_callback set_option_value,
|
||||
bool requires_confirmation = false,
|
||||
bool is_internal = false
|
||||
);
|
||||
virtual ~ConfigPageOptionsMenu() = default;
|
||||
|
||||
void set_check_page_dirty_callback(check_page_dirty_callback callback) { check_page_dirty = callback; }
|
||||
void set_on_apply_callback(std::function<void()> callback);
|
||||
void set_get_config_option_updates_callback(get_config_option_updates_callback callback) {
|
||||
get_config_option_updates = callback;
|
||||
if (clear_config_option_updates != nullptr) {
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
void set_clear_config_option_updates_callback(clear_config_option_updates_callback callback) {
|
||||
clear_config_option_updates = callback;
|
||||
if (get_config_option_updates != nullptr) {
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "ui_config_tab_controls.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
#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
|
||||
|
||||
Element* ConfigTabControls::create_tab_contents(recompui::ContextId context, Element* parent) {
|
||||
Modal *modal = get_parent_modal();
|
||||
apply_set_first_focusable_below_tabs_callback();
|
||||
Element *active_tab = modal != nullptr ? modal->get_active_tab() : nullptr;
|
||||
|
||||
ConfigPage *controls_page = context.create_element<ConfigPageControls>(
|
||||
parent,
|
||||
recompinput::get_num_players(),
|
||||
temp_game_input_contexts,
|
||||
active_tab,
|
||||
set_first_focusable_below_tabs
|
||||
);
|
||||
|
||||
return controls_page;
|
||||
}
|
||||
|
||||
ConfigTabControls config_tab_controls = ConfigTabControls();
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "../elements/ui_element.h"
|
||||
#include "../elements/ui_modal.h"
|
||||
#include "ui_config_page_controls.h"
|
||||
|
||||
namespace recompui {
|
||||
class ConfigTabControls : public TabContext {
|
||||
public:
|
||||
ConfigTabControls() : TabContext("Controls") {}
|
||||
Element* create_tab_contents(recompui::ContextId context, Element* parent) override;
|
||||
};
|
||||
|
||||
extern ConfigTabControls config_tab_controls;
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,385 @@
|
||||
#include "ui_config_tab_graphics.h"
|
||||
#include "banjo_render.h"
|
||||
|
||||
namespace recompui {
|
||||
recomp::config::Config config_graphics("Graphics", "graphics_2", true);
|
||||
ConfigTabGraphics config_tab_graphics(&config_graphics);
|
||||
|
||||
using EnumOptionVector = const std::vector<recomp::config::ConfigOptionEnumOption>;
|
||||
|
||||
namespace gfx_keys {
|
||||
const std::string developer_mode = "developer_mode";
|
||||
const std::string res_option = "res_option";
|
||||
const std::string wm_option = "wm_option";
|
||||
const std::string hr_option = "hr_option";
|
||||
const std::string api_option = "api_option";
|
||||
const std::string ar_option = "ar_option";
|
||||
const std::string msaa_option = "msaa_option";
|
||||
const std::string rr_option = "rr_option";
|
||||
const std::string hpfb_option = "hpfb_option";
|
||||
const std::string rr_manual_value = "rr_manual_value";
|
||||
const std::string ds_option = "ds_option";
|
||||
}
|
||||
|
||||
static bool is_steam_deck = false;
|
||||
|
||||
ultramodern::renderer::WindowMode wm_default() {
|
||||
return is_steam_deck ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed;
|
||||
}
|
||||
|
||||
#ifdef __gnu_linux__
|
||||
void detect_steam_deck() {
|
||||
// Check if the board vendor is Valve.
|
||||
std::ifstream board_vendor_file("/sys/devices/virtual/dmi/id/board_vendor");
|
||||
std::string line;
|
||||
if (std::getline(board_vendor_file, line).good() && line == "Valve") {
|
||||
is_steam_deck = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the SteamDeck variable is set to 1.
|
||||
const char* steam_deck_env = getenv("SteamDeck");
|
||||
if (steam_deck_env != nullptr && std::string{steam_deck_env} == "1") {
|
||||
is_steam_deck = true;
|
||||
return;
|
||||
}
|
||||
|
||||
is_steam_deck = false;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
void detect_steam_deck() { is_steam_deck = false; }
|
||||
#endif
|
||||
|
||||
EnumOptionVector resolution_options = {
|
||||
{ultramodern::renderer::Resolution::Original, "Original", "Original"},
|
||||
{ultramodern::renderer::Resolution::Original2x, "Original2x", "Original 2x"},
|
||||
{ultramodern::renderer::Resolution::Auto, "Auto", "Auto"},
|
||||
};
|
||||
|
||||
enum class DownsamplingOption {
|
||||
Off = 0,
|
||||
X2 = 2,
|
||||
X4 = 4,
|
||||
};
|
||||
EnumOptionVector downsampling_options = {
|
||||
{DownsamplingOption::Off, "Off"},
|
||||
{DownsamplingOption::X2, "2x"},
|
||||
{DownsamplingOption::X4, "4x"},
|
||||
};
|
||||
|
||||
EnumOptionVector window_mode_options = {
|
||||
{ultramodern::renderer::WindowMode::Windowed, "Windowed"},
|
||||
{ultramodern::renderer::WindowMode::Fullscreen, "Fullscreen"}
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define ALLOW_D3D12
|
||||
#endif
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
#define ALLOW_VULKAN
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#define ALLOW_METAL
|
||||
#endif
|
||||
|
||||
EnumOptionVector graphics_api_options = {
|
||||
{ultramodern::renderer::GraphicsApi::Auto, "Auto"},
|
||||
#ifdef ALLOW_D3D12
|
||||
{ultramodern::renderer::GraphicsApi::D3D12, "D3D12"},
|
||||
#endif
|
||||
#ifdef ALLOW_VULKAN
|
||||
{ultramodern::renderer::GraphicsApi::Vulkan, "Vulkan"},
|
||||
#endif
|
||||
#ifdef ALLOW_METAL
|
||||
{ultramodern::renderer::GraphicsApi::Metal, "Metal"},
|
||||
#endif
|
||||
};
|
||||
|
||||
EnumOptionVector aspect_ratio_options = {
|
||||
{ultramodern::renderer::AspectRatio::Original, "Original"},
|
||||
{ultramodern::renderer::AspectRatio::Expand, "Expand"},
|
||||
// {ultramodern::renderer::AspectRatio::Manual, "Manual"},
|
||||
};
|
||||
|
||||
EnumOptionVector antialiasing_options = {
|
||||
{ultramodern::renderer::Antialiasing::None, "None"},
|
||||
{ultramodern::renderer::Antialiasing::MSAA2X, "MSAA2X", "2X"},
|
||||
{ultramodern::renderer::Antialiasing::MSAA4X, "MSAA4X", "4X"},
|
||||
// {ultramodern::renderer::Antialiasing::MSAA8X, "MSAA8X"},
|
||||
};
|
||||
|
||||
EnumOptionVector refresh_rate_options = {
|
||||
{ultramodern::renderer::RefreshRate::Original, "Original"},
|
||||
{ultramodern::renderer::RefreshRate::Display, "Display"},
|
||||
{ultramodern::renderer::RefreshRate::Manual, "Manual"},
|
||||
};
|
||||
|
||||
EnumOptionVector hpfb_options = {
|
||||
{ultramodern::renderer::HighPrecisionFramebuffer::Auto, "Auto"},
|
||||
{ultramodern::renderer::HighPrecisionFramebuffer::On, "On"},
|
||||
{ultramodern::renderer::HighPrecisionFramebuffer::Off, "Off"},
|
||||
};
|
||||
|
||||
EnumOptionVector hud_ratio_mode_options = {
|
||||
{ultramodern::renderer::HUDRatioMode::Original, "Original"},
|
||||
{ultramodern::renderer::HUDRatioMode::Clamp16x9, "Clamp16x9", "16:9"},
|
||||
{ultramodern::renderer::HUDRatioMode::Full, "Expand"},
|
||||
};
|
||||
|
||||
std::string get_downsampling_details(ultramodern::renderer::Resolution res_option, DownsamplingOption ds_option) {
|
||||
switch (res_option) {
|
||||
default:
|
||||
case ultramodern::renderer::Resolution::Auto:
|
||||
return "Downsampling is not available at auto resolution";
|
||||
case ultramodern::renderer::Resolution::Original:
|
||||
if (ds_option == DownsamplingOption::X2) {
|
||||
return "Rendered in 480p and scaled to 240p";
|
||||
} else if (ds_option == DownsamplingOption::X4) {
|
||||
return "Rendered in 960p and scaled to 240p";
|
||||
}
|
||||
return "";
|
||||
case ultramodern::renderer::Resolution::Original2x:
|
||||
if (ds_option == DownsamplingOption::X2) {
|
||||
return "Rendered in 960p and scaled to 480p";
|
||||
} else if (ds_option == DownsamplingOption::X4) {
|
||||
return "Rendered in 4K and scaled to 480p";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
using OptionChangeContext = recomp::config::OptionChangeContext;
|
||||
|
||||
template <typename T = uint32_t>
|
||||
T get_graphics_enum_value(const std::string& key, OptionChangeContext change_context = OptionChangeContext::Permanent) {
|
||||
if (change_context == OptionChangeContext::Temporary) {
|
||||
return static_cast<T>(std::get<uint32_t>(config_graphics.get_temp_option_value(key)));
|
||||
} else {
|
||||
return static_cast<T>(std::get<uint32_t>(config_graphics.get_option_value(key)));
|
||||
}
|
||||
}
|
||||
template <typename T = uint32_t>
|
||||
T get_graphics_number_value(const std::string& key, OptionChangeContext change_context = OptionChangeContext::Permanent) {
|
||||
if (change_context == OptionChangeContext::Temporary) {
|
||||
return static_cast<T>(std::get<double>(config_graphics.get_temp_option_value(key)));
|
||||
} else {
|
||||
return static_cast<T>(std::get<double>(config_graphics.get_option_value(key)));
|
||||
}
|
||||
}
|
||||
|
||||
bool get_graphics_bool_value(const std::string& key, OptionChangeContext change_context = OptionChangeContext::Permanent) {
|
||||
if (change_context == OptionChangeContext::Temporary) {
|
||||
return std::get<bool>(config_graphics.get_temp_option_value(key));
|
||||
} else {
|
||||
return std::get<bool>(config_graphics.get_option_value(key));
|
||||
}
|
||||
}
|
||||
|
||||
void determine_downsampling_display(ultramodern::renderer::Resolution res_option, OptionChangeContext change_context) {
|
||||
DownsamplingOption ds_opt = get_graphics_enum_value<DownsamplingOption>(gfx_keys::ds_option, change_context);
|
||||
config_graphics.update_option_enum_details(gfx_keys::ds_option, get_downsampling_details(res_option, ds_opt));
|
||||
}
|
||||
|
||||
std::string get_framerate_text() {
|
||||
return
|
||||
"Sets the game's output framerate. This option does not affect gameplay."
|
||||
"<br />"
|
||||
"<br />"
|
||||
"Note: If you have issues with <recomp-color primary>Display</recomp-color> mode while using an external frame limiter, use <recomp-color primary>Manual</recomp-color> mode instead and configure it to that same frame limit."
|
||||
"<br />"
|
||||
"<br />"
|
||||
"<recomp-color primary>Detected display refresh rate: " + std::to_string(ultramodern::get_display_refresh_rate()) + "hz</recomp-color>";
|
||||
}
|
||||
|
||||
void save_graphics_config() {
|
||||
ultramodern::renderer::GraphicsConfig new_config;
|
||||
new_config.developer_mode = get_graphics_bool_value(gfx_keys::developer_mode);
|
||||
new_config.res_option = get_graphics_enum_value<ultramodern::renderer::Resolution>(gfx_keys::res_option);
|
||||
new_config.wm_option = get_graphics_enum_value<ultramodern::renderer::WindowMode>(gfx_keys::wm_option);
|
||||
new_config.hr_option = get_graphics_enum_value<ultramodern::renderer::HUDRatioMode>(gfx_keys::hr_option);
|
||||
new_config.api_option = get_graphics_enum_value<ultramodern::renderer::GraphicsApi>(gfx_keys::api_option);
|
||||
new_config.ar_option = get_graphics_enum_value<ultramodern::renderer::AspectRatio>(gfx_keys::ar_option);
|
||||
new_config.msaa_option = get_graphics_enum_value<ultramodern::renderer::Antialiasing>(gfx_keys::msaa_option);
|
||||
new_config.rr_option = get_graphics_enum_value<ultramodern::renderer::RefreshRate>(gfx_keys::rr_option);
|
||||
new_config.hpfb_option = get_graphics_enum_value<ultramodern::renderer::HighPrecisionFramebuffer>(gfx_keys::hpfb_option);
|
||||
new_config.rr_manual_value = get_graphics_number_value<int>(gfx_keys::rr_manual_value);
|
||||
|
||||
new_config.ds_option = get_graphics_enum_value<int>(gfx_keys::ds_option);
|
||||
|
||||
ultramodern::renderer::set_graphics_config(new_config);
|
||||
}
|
||||
|
||||
void ConfigTabGraphics::on_init() {
|
||||
config_graphics.set_save_callback(save_graphics_config);
|
||||
|
||||
config_graphics.add_bool_option(
|
||||
gfx_keys::developer_mode,
|
||||
"Dev Mode",
|
||||
"Enables developer features.",
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::res_option,
|
||||
"Resolution",
|
||||
"Sets the output resolution of the game. <recomp-color primary>Original</recomp-color> matches the game's original 240p resolution. <recomp-color primary>Original 2x</recomp-color> will render at 480p. <recomp-color primary>Auto</recomp-color> will scale based on the game window's resolution.",
|
||||
resolution_options,
|
||||
ultramodern::renderer::Resolution::Auto
|
||||
);
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::ds_option,
|
||||
"Downsampling Quality",
|
||||
"Renders at a higher resolution and scales it down to the output resolution for increased quality. Only available in <recomp-color primary>Original</recomp-color> and <recomp-color primary>Original 2x</recomp-color> resolution."
|
||||
"<br />"
|
||||
"<br />"
|
||||
"Note: <recomp-color primary>4x</recomp-color> downsampling quality at <recomp-color primary>Original 2x</recomp-color> resolution may cause performance issues on low end devices, as it will cause the game to render <recomp-color warning>at almost 4k internal resolution</recomp-color>.",
|
||||
downsampling_options,
|
||||
DownsamplingOption::Off
|
||||
);
|
||||
config_graphics.add_option_change_callback(
|
||||
gfx_keys::res_option,
|
||||
[](recomp::config::ConfigValueVariant cur_value, recomp::config::ConfigValueVariant prev_value, OptionChangeContext change_context) {
|
||||
auto new_opt = static_cast<ultramodern::renderer::Resolution>(std::get<uint32_t>(cur_value));
|
||||
determine_downsampling_display(new_opt, change_context);
|
||||
}
|
||||
);
|
||||
config_graphics.add_option_change_callback(
|
||||
gfx_keys::ds_option,
|
||||
[](recomp::config::ConfigValueVariant cur_value, recomp::config::ConfigValueVariant prev_value, OptionChangeContext change_context) {
|
||||
auto resolution_opt = get_graphics_enum_value<ultramodern::renderer::Resolution>(gfx_keys::res_option, change_context);
|
||||
determine_downsampling_display(resolution_opt, change_context);
|
||||
}
|
||||
);
|
||||
config_graphics.add_option_disable_dependency(
|
||||
gfx_keys::ds_option,
|
||||
gfx_keys::res_option,
|
||||
ultramodern::renderer::Resolution::Auto
|
||||
);
|
||||
|
||||
config_graphics.on_json_parse_option(gfx_keys::ds_option, [](const nlohmann::json& j) {
|
||||
return j.get<uint32_t>();
|
||||
});
|
||||
|
||||
config_graphics.on_json_serialize_option(gfx_keys::ds_option, [](const recomp::config::ConfigValueVariant& value) {
|
||||
return nlohmann::json(std::get<uint32_t>(value));
|
||||
});
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::ar_option,
|
||||
"Aspect Ratio",
|
||||
"Sets the horizontal aspect ratio. <recomp-color primary>Original</recomp-color> uses the game's original 4:3 aspect ratio. <recomp-color primary>Expand</recomp-color> will adjust to match the game window's aspect ratio.",
|
||||
aspect_ratio_options,
|
||||
ultramodern::renderer::AspectRatio::Expand
|
||||
);
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::wm_option,
|
||||
"Window Mode",
|
||||
"Sets whether the game should display <recomp-color primary>Windowed</recomp-color> or <recomp-color primary>Fullscreen</recomp-color>. You can also use <recomp-color primary>F11</recomp-color> or <recomp-color primary>Alt + Enter</recomp-color> to toggle this option.",
|
||||
window_mode_options,
|
||||
wm_default()
|
||||
);
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::rr_option,
|
||||
"Framerate",
|
||||
get_framerate_text(),
|
||||
refresh_rate_options,
|
||||
ultramodern::renderer::RefreshRate::Display
|
||||
);
|
||||
config_graphics.add_option_change_callback(
|
||||
gfx_keys::res_option,
|
||||
[](recomp::config::ConfigValueVariant cur_value, recomp::config::ConfigValueVariant prev_value, OptionChangeContext change_context) {
|
||||
auto new_opt = static_cast<ultramodern::renderer::Resolution>(std::get<uint32_t>(cur_value));
|
||||
determine_downsampling_display(new_opt, change_context);
|
||||
}
|
||||
);
|
||||
config_graphics.add_number_option(
|
||||
gfx_keys::rr_manual_value,
|
||||
"",
|
||||
get_framerate_text(),
|
||||
20.0, 240.0, 1.0, 0, false, 60.0
|
||||
);
|
||||
|
||||
config_graphics.add_option_hidden_dependency(
|
||||
gfx_keys::rr_manual_value,
|
||||
gfx_keys::rr_option,
|
||||
ultramodern::renderer::RefreshRate::Original,
|
||||
ultramodern::renderer::RefreshRate::Display
|
||||
);
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::msaa_option,
|
||||
"MS Anti-Aliasing",
|
||||
"Sets the multisample anti-aliasing (MSAA) quality level. This reduces jagged edges in the final image at the expense of rendering performance."
|
||||
"<br />"
|
||||
"<br />"
|
||||
"<recomp-color primary>Note: This option won't be available if your GPU does not support programmable MSAA sample positions, as it is currently required to avoid rendering glitches.</recomp-color>",
|
||||
antialiasing_options,
|
||||
ultramodern::renderer::Antialiasing::MSAA2X
|
||||
);
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::hr_option,
|
||||
"HUD Placement",
|
||||
"Adjusts the placement of HUD elements to fit the selected aspect ratio. <recomp-color primary>Expand</recomp-color> will use the aspect ratio of the game's output window.",
|
||||
hud_ratio_mode_options,
|
||||
ultramodern::renderer::HUDRatioMode::Clamp16x9
|
||||
);
|
||||
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::api_option,
|
||||
"Graphics API",
|
||||
"Selects the graphics API to use.",
|
||||
graphics_api_options,
|
||||
ultramodern::renderer::GraphicsApi::Auto
|
||||
);
|
||||
config_graphics.add_option_hidden_dependency(
|
||||
gfx_keys::api_option,
|
||||
gfx_keys::developer_mode,
|
||||
false
|
||||
);
|
||||
config_graphics.add_enum_option(
|
||||
gfx_keys::hpfb_option,
|
||||
"High Precision Framebuffer",
|
||||
"Sets whether to use a high precision framebuffer.",
|
||||
hpfb_options,
|
||||
ultramodern::renderer::HighPrecisionFramebuffer::Off
|
||||
);
|
||||
config_graphics.add_option_hidden_dependency(
|
||||
gfx_keys::hpfb_option,
|
||||
gfx_keys::developer_mode,
|
||||
false
|
||||
);
|
||||
|
||||
config_graphics.load_config();
|
||||
save_graphics_config();
|
||||
}
|
||||
|
||||
void update_msaa_supported(bool supported) {
|
||||
if (!supported) {
|
||||
config_graphics.update_option_enum_details(
|
||||
gfx_keys::msaa_option,
|
||||
supported ? "Available" : "Not available (missing sample positions support)"
|
||||
);
|
||||
config_graphics.update_option_disabled(
|
||||
gfx_keys::msaa_option,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
auto max_msaa = banjo::renderer::RT64MaxMSAA();
|
||||
if (max_msaa < RT64::UserConfiguration::Antialiasing::MSAA2X) {
|
||||
config_graphics.update_enum_option_disabled(gfx_keys::msaa_option, static_cast<uint32_t>(ultramodern::renderer::Antialiasing::MSAA2X), true);
|
||||
}
|
||||
if (max_msaa < RT64::UserConfiguration::Antialiasing::MSAA4X) {
|
||||
config_graphics.update_enum_option_disabled(gfx_keys::msaa_option, static_cast<uint32_t>(ultramodern::renderer::Antialiasing::MSAA4X), true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "librecomp/config.hpp"
|
||||
#include "ui_config_tab_manifest.h"
|
||||
|
||||
namespace recompui {
|
||||
class ConfigTabGraphics : public ConfigTab {
|
||||
public:
|
||||
ConfigTabGraphics(recomp::config::Config *config) : ConfigTab(config) {}
|
||||
|
||||
void on_init() override;
|
||||
};
|
||||
|
||||
extern recomp::config::Config config_graphics;
|
||||
extern ConfigTabGraphics config_tab_graphics;
|
||||
|
||||
void update_msaa_supported(bool supported);
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "ui_config_tab_manifest.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ConfigTab::ConfigTab(recomp::config::Config *config) : TabContext(config->name),
|
||||
config(config),
|
||||
callbacks(ConfigOptionPropertyCallbacks(
|
||||
// get_configuration
|
||||
[this](const std::string &option_id) {
|
||||
auto schema = this->config->get_config_schema();
|
||||
size_t option_index = schema.options_by_id.at(option_id);
|
||||
auto &option = schema.options[option_index];
|
||||
return option.variant;
|
||||
},
|
||||
// get_value
|
||||
[this](const std::string &option_id) {
|
||||
if (this->config->requires_confirmation) {
|
||||
return this->config->get_temp_option_value(option_id);
|
||||
}
|
||||
return this->config->get_option_value(option_id);
|
||||
},
|
||||
// get_name
|
||||
[this](const std::string &option_id) {
|
||||
auto schema = this->config->get_config_schema();
|
||||
size_t option_index = schema.options_by_id.at(option_id);
|
||||
return schema.options[option_index].name;
|
||||
},
|
||||
// get_description
|
||||
[this](const std::string &option_id) {
|
||||
auto schema = this->config->get_config_schema();
|
||||
size_t option_index = schema.options_by_id.at(option_id);
|
||||
return schema.options[option_index].description;
|
||||
},
|
||||
// get_hidden
|
||||
[this](const std::string &option_id) {
|
||||
auto schema = this->config->get_config_schema();
|
||||
size_t option_index = schema.options_by_id.at(option_id);
|
||||
return this->config->is_config_option_hidden(option_index);
|
||||
},
|
||||
// get_disabled
|
||||
[this](const std::string &option_id) {
|
||||
auto schema = this->config->get_config_schema();
|
||||
size_t option_index = schema.options_by_id.at(option_id);
|
||||
return this->config->is_config_option_disabled(option_index);
|
||||
},
|
||||
// get_enum_option_details
|
||||
[this](const std::string &option_id) {
|
||||
auto schema = this->config->get_config_schema();
|
||||
size_t option_index = schema.options_by_id.at(option_id);
|
||||
return this->config->get_enum_option_details(option_index);
|
||||
},
|
||||
// get_enum_option_disabled
|
||||
[this](const std::string &option_id, uint32_t enum_index) {
|
||||
auto schema = this->config->get_config_schema();
|
||||
size_t option_index = schema.options_by_id.at(option_id);
|
||||
return this->config->get_enum_option_disabled(option_index, enum_index);
|
||||
}
|
||||
)) {};
|
||||
|
||||
Element* ConfigTab::create_tab_contents(recompui::ContextId context, Element* parent) {
|
||||
ConfigPageOptionsMenu *conf_page = context.create_element<ConfigPageOptionsMenu>(
|
||||
parent,
|
||||
config,
|
||||
callbacks,
|
||||
[this](const std::string &option_id, recomp::config::ConfigValueVariant value) {
|
||||
this->config->set_option_value(option_id, value);
|
||||
},
|
||||
config->requires_confirmation,
|
||||
true
|
||||
);
|
||||
|
||||
conf_page->set_get_config_option_updates_callback([this]() {
|
||||
return this->config->get_config_option_updates();
|
||||
});
|
||||
|
||||
conf_page->set_clear_config_option_updates_callback([this]() {
|
||||
this->config->clear_config_option_updates();
|
||||
});
|
||||
|
||||
if (config->requires_confirmation) {
|
||||
conf_page->set_on_apply_callback([this]() {
|
||||
this->config->save_config();
|
||||
});
|
||||
conf_page->set_check_page_dirty_callback([this]() {
|
||||
return this->config->is_dirty();
|
||||
});
|
||||
}
|
||||
|
||||
return conf_page;
|
||||
};
|
||||
|
||||
bool ConfigTab::can_be_closed() {
|
||||
if (!config->requires_confirmation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config->is_dirty()) {
|
||||
// TODO: show prompt to apply/cancel changes
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigTab::on_tab_close() {
|
||||
config->save_config();
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "librecomp/config.hpp"
|
||||
#include "ui_config_common.h"
|
||||
#include "ui_config_page_options_menu.h"
|
||||
#include "../elements/ui_element.h"
|
||||
#include "../elements/ui_modal.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ConfigTab : public TabContext {
|
||||
private:
|
||||
recomp::config::Config *config = nullptr;
|
||||
ConfigOptionPropertyCallbacks callbacks;
|
||||
public:
|
||||
ConfigTab(recomp::config::Config *config);
|
||||
|
||||
Element* create_tab_contents(recompui::ContextId context, Element* parent) override;
|
||||
bool can_be_closed() override;
|
||||
void on_tab_close() override;
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -248,6 +248,7 @@ recompui::ContextId recompui::create_context() {
|
||||
root->set_width(100.0f, Unit::Percent);
|
||||
root->set_height(100.0f, Unit::Percent);
|
||||
root->set_display(Display::Flex);
|
||||
root->set_as_root_document();
|
||||
|
||||
ret.close();
|
||||
|
||||
@@ -583,6 +584,11 @@ recompui::Style* recompui::ContextId::create_style() {
|
||||
return add_resource_impl(std::make_unique<Style>());
|
||||
}
|
||||
|
||||
void recompui::ContextId::destroy_resource(Element* resource) {
|
||||
recompui::report_removed_element(resource->base);
|
||||
destroy_resource(resource->resource_id);
|
||||
}
|
||||
|
||||
void recompui::ContextId::destroy_resource(Style* resource) {
|
||||
destroy_resource(resource->resource_id);
|
||||
}
|
||||
@@ -731,3 +737,36 @@ recompui::ContextId recompui::get_context_from_document(Rml::ElementDocument* do
|
||||
}
|
||||
return find_it->second;
|
||||
}
|
||||
|
||||
recompui::Element* recompui::ContextId::get_focused_element() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::AddResourceWithoutOpen);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::AddResourceToWrongContext);
|
||||
}
|
||||
|
||||
auto doc = get_document();
|
||||
if (doc == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
Rml::Context *rml_context = doc->GetContext();
|
||||
if (rml_context == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
Rml::Element *focused = rml_context->GetFocusElement();
|
||||
if (focused == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& resource : opened_context->resources) {
|
||||
recompui::Element *element = static_cast<Element*>(resource.get());
|
||||
if (element->id == focused->GetId()) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,14 @@ namespace recompui {
|
||||
|
||||
Style* create_style();
|
||||
|
||||
void destroy_resource(Element* resource);
|
||||
void destroy_resource(Style* resource);
|
||||
void destroy_resource(ResourceId resource);
|
||||
void clear_children();
|
||||
|
||||
Rml::ElementDocument* get_document();
|
||||
Element* get_root_element();
|
||||
Element* get_focused_element();
|
||||
Element* get_autofocus_element();
|
||||
void set_autofocus_element(Element* element);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ui_binding_button.h"
|
||||
#include "ui_theme.h"
|
||||
#include "recomp_ui.h"
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
namespace recompui {
|
||||
@@ -36,8 +37,15 @@ namespace recompui {
|
||||
ContextId context = get_current_context();
|
||||
|
||||
bound_text_el = context.create_element<Element>(this, 0, "div", true);
|
||||
bound_text_el->set_text(mapped_binding);
|
||||
apply_binding_style();
|
||||
unknown_svg_el = context.create_element<Svg>(this, "icons/Question.svg");
|
||||
unknown_svg_el->set_position(Position::Absolute);
|
||||
unknown_svg_el->set_width(32);
|
||||
unknown_svg_el->set_height(32);
|
||||
unknown_svg_el->set_top(50.0f, recompui::Unit::Percent);
|
||||
unknown_svg_el->set_left(50.0f, recompui::Unit::Percent);
|
||||
unknown_svg_el->set_translate_2D(-50.0f, -50.0f, recompui::Unit::Percent);
|
||||
set_binding(mapped_binding);
|
||||
|
||||
recording_parent = context.create_element<Element>(this);
|
||||
recording_circle = context.create_element<Element>(recording_parent);
|
||||
@@ -101,11 +109,15 @@ namespace recompui {
|
||||
}
|
||||
|
||||
void BindingButton::apply_binding_style() {
|
||||
bound_text_el->set_position(Position::Absolute);
|
||||
bound_text_el->set_top(50.0f, recompui::Unit::Percent);
|
||||
bound_text_el->set_left(50.0f, recompui::Unit::Percent);
|
||||
bound_text_el->set_translate_2D(-50.0f, -50.0f, recompui::Unit::Percent);
|
||||
bound_text_el->set_font_family("promptfont");
|
||||
bound_text_el->set_font_size(40.0f);
|
||||
bound_text_el->set_font_size(32.0f);
|
||||
bound_text_el->set_font_style(FontStyle::Normal);
|
||||
bound_text_el->set_font_weight(400);
|
||||
bound_text_el->set_line_height(40.0f);
|
||||
bound_text_el->set_line_height(32.0f);
|
||||
bound_text_el->set_opacity(1);
|
||||
}
|
||||
|
||||
@@ -115,7 +127,15 @@ namespace recompui {
|
||||
|
||||
void BindingButton::set_binding(const std::string &binding) {
|
||||
this->mapped_binding = binding;
|
||||
bound_text_el->set_text(mapped_binding);
|
||||
if (binding == recompui::unknown_input) {
|
||||
bound_text_el->set_text("");
|
||||
bound_text_el->set_display(Display::None);
|
||||
unknown_svg_el->set_display(Display::Block);
|
||||
} else {
|
||||
bound_text_el->set_text(mapped_binding);
|
||||
bound_text_el->set_display(Display::Block);
|
||||
unknown_svg_el->set_display(Display::None);
|
||||
}
|
||||
}
|
||||
|
||||
void BindingButton::set_is_binding(bool is_binding) {
|
||||
@@ -123,10 +143,12 @@ namespace recompui {
|
||||
|
||||
if (is_binding) {
|
||||
bound_text_el->set_opacity(0);
|
||||
unknown_svg_el->set_opacity(0);
|
||||
recording_parent->set_opacity(1);
|
||||
queue_update();
|
||||
} else {
|
||||
bound_text_el->set_opacity(1);
|
||||
unknown_svg_el->set_opacity(1);
|
||||
recording_parent->set_opacity(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace recompui {
|
||||
std::string mapped_binding; // promptfont representation of the binding.
|
||||
|
||||
Element *bound_text_el;
|
||||
Svg *unknown_svg_el;
|
||||
Element *recording_parent;
|
||||
Element *recording_circle;
|
||||
Element *recording_edge;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ui_button.h"
|
||||
#include "ui_label.h"
|
||||
#include "ui_pseudo_border.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -48,6 +49,9 @@ namespace recompui {
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
auto focus_border = context.create_element<FocusBorder>(this, true);
|
||||
focus_border->set_border_radius(theme::border::radius_md + theme::border::width * 4.0f);
|
||||
|
||||
switch (size) {
|
||||
case ButtonSize::Small: {
|
||||
auto label = context.create_element<Label>(this, text, LabelStyle::Annotation);
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace recompui {
|
||||
for (const auto &function : clicked_callbacks) {
|
||||
function(click.x, click.y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::MouseButton: {
|
||||
if (is_enabled()) {
|
||||
@@ -28,8 +28,8 @@ namespace recompui {
|
||||
function(mousebutton.x, mousebutton.y);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
@@ -54,8 +54,8 @@ namespace recompui {
|
||||
for (const auto &function : dragged_callbacks) {
|
||||
function(drag.x, drag.y, drag.phase);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@@ -74,4 +74,4 @@ namespace recompui {
|
||||
dragged_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -51,6 +51,14 @@ namespace recompui {
|
||||
right = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexEnd, 0);
|
||||
set_header_footer_side_styles(right);
|
||||
}
|
||||
|
||||
void ConfigHeaderFooter::hide() {
|
||||
set_display(Display::None);
|
||||
}
|
||||
|
||||
void ConfigHeaderFooter::show() {
|
||||
set_display(Display::Flex);
|
||||
}
|
||||
|
||||
static void set_config_body_side_styles(Element *el) {
|
||||
el->set_flex_grow(1.0f);
|
||||
@@ -78,7 +86,7 @@ namespace recompui {
|
||||
set_config_body_side_styles(right);
|
||||
}
|
||||
|
||||
ConfigPage::ConfigPage(Element *parent) : Element(parent, 0, "div", false) {
|
||||
ConfigPage::ConfigPage(Element *parent, uint32_t events_enabled) : Element(parent, events_enabled, "div", false) {
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
|
||||
@@ -92,40 +100,37 @@ namespace recompui {
|
||||
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);
|
||||
header->hide();
|
||||
|
||||
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);
|
||||
footer->hide();
|
||||
}
|
||||
|
||||
ConfigHeaderFooter* ConfigPage::add_header() {
|
||||
header->set_visibility(Visibility::Visible);
|
||||
header->show();
|
||||
return header;
|
||||
}
|
||||
|
||||
void ConfigPage::hide_header() {
|
||||
header->set_visibility(Visibility::Hidden);
|
||||
header->hide();
|
||||
}
|
||||
|
||||
ConfigHeaderFooter* ConfigPage::add_footer() {
|
||||
footer->set_visibility(Visibility::Visible);
|
||||
footer->show();
|
||||
set_border_bottom_left_radius(0);
|
||||
set_border_bottom_right_radius(0);
|
||||
return footer;
|
||||
}
|
||||
|
||||
void ConfigPage::hide_footer() {
|
||||
footer->set_visibility(Visibility::Hidden);
|
||||
footer->hide();
|
||||
set_border_bottom_left_radius(theme::border::radius_lg);
|
||||
set_border_bottom_right_radius(theme::border::radius_lg);
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ namespace recompui {
|
||||
Element *left;
|
||||
Element *right;
|
||||
bool is_header;
|
||||
Display prev_display = Display::Block;
|
||||
|
||||
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; }
|
||||
void hide();
|
||||
void show();
|
||||
};
|
||||
|
||||
class ConfigBody : public Element {
|
||||
@@ -37,7 +40,7 @@ namespace recompui {
|
||||
|
||||
std::string_view get_type_name() override { return "ConfigPage"; }
|
||||
public:
|
||||
ConfigPage(Element *parent);
|
||||
ConfigPage(Element *parent, uint32_t events_enabled = 0);
|
||||
ConfigHeaderFooter *add_header();
|
||||
void hide_header();
|
||||
ConfigHeaderFooter *add_footer();
|
||||
|
||||
+509
-15
@@ -9,6 +9,451 @@
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Element *Element::get_nav_parent() {
|
||||
Element *cur = parent;
|
||||
while (cur != nullptr) {
|
||||
if (cur->is_nav_container) {
|
||||
return cur;
|
||||
}
|
||||
cur = cur->parent;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Element::set_as_navigation_container(NavigationType nav_type) {
|
||||
is_nav_container = true;
|
||||
this->nav_type = nav_type;
|
||||
|
||||
// set_border_left_width(1.0f);
|
||||
// set_border_left_color(theme::color::Warning);
|
||||
Element *parent_nav = get_nav_parent();
|
||||
if (parent_nav != nullptr) {
|
||||
parent_nav->nav_children.push_back(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_as_primary_focus(bool is_primary_focus) {
|
||||
this->is_primary_focus = is_primary_focus;
|
||||
// if (is_primary_focus) {
|
||||
// set_border_right_width(1.0f);
|
||||
// set_border_right_color(theme::color::SuccessD);
|
||||
// } else {
|
||||
// set_border_right_width(1.0f);
|
||||
// set_border_right_color(theme::color::WarningD);
|
||||
// }
|
||||
}
|
||||
|
||||
struct RmlPosSize {
|
||||
Rml::Vector2f position;
|
||||
Rml::Vector2f size;
|
||||
Rml::Vector2f center;
|
||||
|
||||
RmlPosSize(Rml::Vector2f position, const Rml::Box& box) : position(position), size(box.GetSize()) {
|
||||
center = position + (size * 0.5f);
|
||||
}
|
||||
|
||||
float get_distance_sq(const RmlPosSize& other) const {
|
||||
auto delta = center - other.center;
|
||||
return delta.SquaredMagnitude();
|
||||
}
|
||||
|
||||
bool can_navigate_vertical(const RmlPosSize& other, int dir) const {
|
||||
if (position.x + size.x < other.position.x + 1) {
|
||||
return false;
|
||||
}
|
||||
if (position.x + 1 > other.position.x + other.size.x) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// bool aligns_horizontally = (
|
||||
// std::abs(position.x - other.position.x) < 5 &&
|
||||
// std::abs((position.x + size.x) - (other.position.x + other.size.x)) < 5
|
||||
// );
|
||||
// bool aligns_horizontally = (
|
||||
// // left side of this box is to the left of the other
|
||||
// position.x < other.position.x + other.size.x &&
|
||||
// // right side of this box is to the right of the other
|
||||
// position.x + size.x > other.position.x
|
||||
// );
|
||||
|
||||
// if (!aligns_horizontally) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (dir == 1) {
|
||||
// Navigating down
|
||||
return is_above(other);
|
||||
} else {
|
||||
// Navigating up
|
||||
return is_below(other);
|
||||
}
|
||||
}
|
||||
|
||||
bool can_navigate_horizontal(const RmlPosSize& other, int dir) const {
|
||||
if (std::abs(position.x - other.position.x) < 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool aligns_vertically = (
|
||||
// top side of this box is above the other
|
||||
position.y < other.position.y + other.size.y &&
|
||||
// bottom side of this box is below the other
|
||||
position.y + size.y > other.position.y
|
||||
);
|
||||
|
||||
if (!aligns_vertically) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dir == 1) {
|
||||
// Navigating right
|
||||
return is_left_of(other);
|
||||
} else {
|
||||
// Navigating left
|
||||
return is_right_of(other);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_left_of(const RmlPosSize& other) const {
|
||||
return position.x <= other.position.x;
|
||||
}
|
||||
bool is_right_of(const RmlPosSize& other) const {
|
||||
return position.x >= other.position.x;
|
||||
}
|
||||
bool is_above(const RmlPosSize& other) const {
|
||||
return position.y <= other.position.y;
|
||||
}
|
||||
bool is_below(const RmlPosSize& other) const {
|
||||
return position.y >= other.position.y;
|
||||
}
|
||||
float vertical_distance(const RmlPosSize& other) const {
|
||||
return std::min(
|
||||
std::abs(position.y - (other.position.y + other.size.y)),
|
||||
std::abs((position.y + size.y) - other.position.y)
|
||||
);
|
||||
}
|
||||
float horizontal_distance(const RmlPosSize& other) const {
|
||||
return std::min(
|
||||
std::abs(position.x - (other.position.x + other.size.x)),
|
||||
std::abs((position.x + size.x) - other.position.x)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Element::CanFocus Element::is_focusable() {
|
||||
if (!base->IsVisible()) {
|
||||
return CanFocus::NoAndNoChildren;
|
||||
}
|
||||
|
||||
if (enabled == false) {
|
||||
return CanFocus::NoAndNoChildren;
|
||||
}
|
||||
|
||||
const Rml::ComputedValues& computed = base->GetComputedValues();
|
||||
|
||||
if (computed.focus() == Rml::Style::Focus::None) {
|
||||
return CanFocus::NoAndNoChildren;
|
||||
}
|
||||
|
||||
if (computed.tab_index() == Rml::Style::TabIndex::Auto) {
|
||||
return CanFocus::Yes;
|
||||
}
|
||||
|
||||
return CanFocus::No;
|
||||
}
|
||||
Element *Element::get_first_focusable_child() {
|
||||
for (auto child : children) {
|
||||
CanFocus res = child->is_focusable();
|
||||
if (res == CanFocus::Yes) {
|
||||
return child;
|
||||
} else if (res == CanFocus::NoAndNoChildren) {
|
||||
continue; // Skip this child, it has no focusable children.
|
||||
} else {
|
||||
Element *focusable_child = child->get_first_focusable_child();
|
||||
if (focusable_child != nullptr) {
|
||||
return focusable_child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int get_element_index(Element *el, std::vector<Element *> elements) {
|
||||
for (int i = 0; i < static_cast<int>(elements.size()); i++) {
|
||||
if (el == elements[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Element *Element::try_grid_navigation(
|
||||
int nav_dir,
|
||||
int cur_element_index
|
||||
) {
|
||||
auto *grid_parent = get_nav_parent();
|
||||
NavigationType parent_check_type = nav_type == NavigationType::GridCol ? NavigationType::GridRow : NavigationType::GridCol;
|
||||
if (grid_parent == nullptr || grid_parent->nav_type != parent_check_type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int parent_element_index = get_element_index(this, grid_parent->nav_children);
|
||||
if (parent_element_index < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int next_grid_index = parent_element_index + nav_dir;
|
||||
if (next_grid_index >= 0 && next_grid_index < grid_parent->nav_children.size()) {
|
||||
Element *adjacent = grid_parent->nav_children[next_grid_index];
|
||||
if (cur_element_index >= 0 && cur_element_index < adjacent->nav_children.size()) {
|
||||
return adjacent->nav_children[cur_element_index];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Element *Element::try_get_nav_direction(
|
||||
int vertical_nav,
|
||||
int horizontal_nav,
|
||||
Element *cur_nav_child
|
||||
) {
|
||||
|
||||
int cur_element_index = get_element_index(cur_nav_child, nav_children);
|
||||
if (cur_element_index == -1) {
|
||||
printf("ERROR: could not find nav child!\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int num_nav_children = static_cast<int>(nav_children.size());
|
||||
int next_nav_index = -1;
|
||||
|
||||
switch (nav_type) {
|
||||
case NavigationType::Auto:
|
||||
break;
|
||||
case NavigationType::GridCol:
|
||||
if (horizontal_nav != 0) {
|
||||
return try_grid_navigation(horizontal_nav, cur_element_index);
|
||||
}
|
||||
// fallthrough
|
||||
case NavigationType::Vertical:
|
||||
if (vertical_nav == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
cur_element_index += vertical_nav;
|
||||
if (cur_element_index >= 0 && cur_element_index < num_nav_children) {
|
||||
return nav_children[cur_element_index];
|
||||
}
|
||||
return nullptr;
|
||||
case NavigationType::GridRow:
|
||||
if (vertical_nav != 0) {
|
||||
return try_grid_navigation(vertical_nav, cur_element_index);
|
||||
}
|
||||
// fallthrough
|
||||
case NavigationType::Horizontal:
|
||||
if (horizontal_nav == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
cur_element_index += horizontal_nav;
|
||||
if (cur_element_index >= 0 && cur_element_index < num_nav_children) {
|
||||
return nav_children[cur_element_index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto focused_box = cur_nav_child->base->GetBox();
|
||||
RmlPosSize this_pos_size(cur_nav_child->base->GetAbsoluteOffset(), focused_box);
|
||||
|
||||
Element *next_focus = nullptr;
|
||||
if (nav_children.size() > 0) {
|
||||
float best_dist = std::numeric_limits<float>::max();
|
||||
for (auto &nav_sibling : nav_children) {
|
||||
if (nav_children[cur_element_index] == nav_sibling) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RmlPosSize sibling_pos_size(nav_sibling->base->GetAbsoluteOffset(), nav_sibling->base->GetBox());
|
||||
if (vertical_nav != 0 && this_pos_size.can_navigate_vertical(sibling_pos_size, vertical_nav)) {
|
||||
float dist = this_pos_size.vertical_distance(sibling_pos_size);
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
next_focus = nav_sibling;
|
||||
}
|
||||
} else if (horizontal_nav != 0 && this_pos_size.can_navigate_horizontal(sibling_pos_size, horizontal_nav)) {
|
||||
float dist = this_pos_size.horizontal_distance(sibling_pos_size);
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
next_focus = nav_sibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next_focus;
|
||||
}
|
||||
|
||||
void Element::get_all_focusable_children(Element *nav_parent) {
|
||||
for (auto child : children) {
|
||||
CanFocus res = child->is_focusable();
|
||||
if (res == CanFocus::Yes) {
|
||||
nav_parent->nav_children.push_back(child);
|
||||
} else if (res == CanFocus::NoAndNoChildren) {
|
||||
continue; // Skip this child, it has no focusable children.
|
||||
} else {
|
||||
child->get_all_focusable_children(nav_parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
|
||||
if (!base->IsVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &child : children) {
|
||||
if (child == cur_focus_element) {
|
||||
nav_parent->nav_children.push_back(child);
|
||||
continue;
|
||||
}
|
||||
if (!child->base->IsVisible() || !child->enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child->is_focusable() == CanFocus::Yes) {
|
||||
// End of the line because it is focusable itself
|
||||
nav_parent->nav_children.push_back(child);
|
||||
} else if (child->is_nav_container) {
|
||||
nav_parent->nav_children.push_back(child);
|
||||
child->nav_children.clear();
|
||||
child->build_navigation(child, cur_focus_element);
|
||||
// didn't find any nav children, check for focus elements
|
||||
if (child->nav_children.size() == 0) {
|
||||
child->get_all_focusable_children(child);
|
||||
}
|
||||
// didn't find any focus elements
|
||||
if (child->nav_children.size() == 0) {
|
||||
nav_parent->nav_children.pop_back();
|
||||
}
|
||||
} else {
|
||||
child->build_navigation(nav_parent, cur_focus_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Element *Element::get_closest_element(std::vector<Element *> &elements) {
|
||||
if (elements.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto this_box = base->GetBox();
|
||||
RmlPosSize this_pos_size(base->GetAbsoluteOffset(), this_box);
|
||||
Element *closest = nullptr;
|
||||
float closest_distance = std::numeric_limits<float>::max();
|
||||
for (auto &element : elements) {
|
||||
auto element_box = element->base->GetBox();
|
||||
RmlPosSize element_pos_size(element->base->GetAbsoluteOffset(), element_box);
|
||||
float distance = this_pos_size.get_distance_sq(element_pos_size);
|
||||
if (distance < closest_distance) {
|
||||
closest_distance = distance;
|
||||
closest = element;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
bool Element::handle_navigation_event(Rml::Event &event) {
|
||||
if (
|
||||
is_root_document &&
|
||||
event.GetId() == Rml::EventId::Keydown &&
|
||||
event.GetPhase() == Rml::EventPhase::Bubble &&
|
||||
event.IsPropagating()
|
||||
) {
|
||||
int key_identifier = event.GetParameter<int>("key_identifier", Rml::Input::KI_UNKNOWN);
|
||||
if (
|
||||
key_identifier != Rml::Input::KI_LEFT &&
|
||||
key_identifier != Rml::Input::KI_RIGHT &&
|
||||
key_identifier != Rml::Input::KI_UP &&
|
||||
key_identifier != Rml::Input::KI_DOWN
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
std::string nav_key_name;
|
||||
switch (key_identifier) {
|
||||
case Rml::Input::KI_UP: nav_key_name = "up"; break;
|
||||
case Rml::Input::KI_DOWN: nav_key_name = "down"; break;
|
||||
case Rml::Input::KI_LEFT: nav_key_name = "left"; break;
|
||||
case Rml::Input::KI_RIGHT: nav_key_name = "right"; break;
|
||||
}
|
||||
printf("Navigation key pressed: %-5s (%d)\n", nav_key_name.c_str(), key_identifier);
|
||||
|
||||
int vertical_nav = 0;
|
||||
int horizontal_nav = 0;
|
||||
switch (key_identifier) {
|
||||
case Rml::Input::KI_UP: vertical_nav = -1; break;
|
||||
case Rml::Input::KI_DOWN: vertical_nav = 1; break;
|
||||
case Rml::Input::KI_LEFT: horizontal_nav = -1; break;
|
||||
case Rml::Input::KI_RIGHT: horizontal_nav = 1; break;
|
||||
}
|
||||
|
||||
auto ctx = get_current_context();
|
||||
auto navigation_from_element = ctx.get_focused_element();
|
||||
if (navigation_from_element == nullptr) {
|
||||
printf(" No focused element, cannot navigate.\n");
|
||||
return false;
|
||||
}
|
||||
nav_children.clear();
|
||||
build_navigation(this, navigation_from_element);
|
||||
printf("ROOT\n");
|
||||
print_nav_hierarchy(0);
|
||||
|
||||
Element *nav_parent = navigation_from_element->get_nav_parent();
|
||||
while (nav_parent != nullptr) {
|
||||
Element *next_focus = nav_parent->try_get_nav_direction(
|
||||
vertical_nav,
|
||||
horizontal_nav,
|
||||
navigation_from_element
|
||||
);
|
||||
|
||||
// If found, dive into the next focus element until finding the bottom most nav container
|
||||
if (next_focus != nullptr) {
|
||||
while (next_focus->nav_children.size() > 0) {
|
||||
bool found_primary = false;
|
||||
for (auto &child : next_focus->nav_children) {
|
||||
if (child->is_primary_focus) {
|
||||
next_focus = child;
|
||||
found_primary = true;
|
||||
printf(" Found primary focus child: %s\n", child->get_id().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_primary) {
|
||||
next_focus = navigation_from_element->get_closest_element(next_focus->nav_children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_focus != nullptr) {
|
||||
event.StopPropagation();
|
||||
next_focus->focus();
|
||||
printf(" Navigated to next focus element: %s\n", next_focus->get_id().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
navigation_from_element = nav_parent;
|
||||
nav_parent = nav_parent->get_nav_parent();
|
||||
printf(" No next focus found, moving up to parent: %s\n", navigation_from_element->get_id().c_str());
|
||||
}
|
||||
|
||||
printf(" No next focus found, reached root document.\n");
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Element::Element(Rml::Element *base) {
|
||||
assert(base != nullptr);
|
||||
|
||||
@@ -114,7 +559,7 @@ void Element::register_event_listeners(uint32_t events_enabled) {
|
||||
base->AddEventListener(Rml::EventId::Change, this);
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::Navigate)) {
|
||||
if (events_enabled & Events(EventType::Navigate, EventType::MenuAction)) {
|
||||
base->AddEventListener(Rml::EventId::Keydown, this);
|
||||
}
|
||||
}
|
||||
@@ -146,7 +591,11 @@ void Element::propagate_disabled(bool disabled) {
|
||||
bool attribute_state = disabled_from_parent || !enabled;
|
||||
if (disabled_attribute != attribute_state) {
|
||||
disabled_attribute = attribute_state;
|
||||
base->SetAttribute("disabled", attribute_state);
|
||||
if (disabled_attribute) {
|
||||
base->SetAttribute("disabled", true);
|
||||
} else {
|
||||
base->RemoveAttribute("disabled");
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::Enable)) {
|
||||
handle_event(Event::enable_event(!attribute_state));
|
||||
@@ -220,22 +669,39 @@ void Element::ProcessEvent(Rml::Event &event) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Rml::EventId::Keydown:
|
||||
switch ((Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0)) {
|
||||
case Rml::Input::KeyIdentifier::KI_LEFT:
|
||||
handle_event(Event::navigate_event(NavDirection::Left));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_UP:
|
||||
handle_event(Event::navigate_event(NavDirection::Up));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_RIGHT:
|
||||
handle_event(Event::navigate_event(NavDirection::Right));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_DOWN:
|
||||
handle_event(Event::navigate_event(NavDirection::Down));
|
||||
case Rml::EventId::Keydown: {
|
||||
auto rml_key = (Rml::Input::KeyIdentifier)event.GetParameter<int>("key_identifier", 0);
|
||||
if (events_enabled & Events(EventType::Navigate)) {
|
||||
if (handle_navigation_event(event)) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (rml_key) {
|
||||
case Rml::Input::KeyIdentifier::KI_LEFT:
|
||||
handle_event(Event::navigate_event(NavDirection::Left));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_UP:
|
||||
handle_event(Event::navigate_event(NavDirection::Up));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_RIGHT:
|
||||
handle_event(Event::navigate_event(NavDirection::Right));
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_DOWN:
|
||||
handle_event(Event::navigate_event(NavDirection::Down));
|
||||
printf("Navigate down\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (events_enabled & Events(EventType::MenuAction)) {
|
||||
MenuAction action = menu_action_mapping::menu_action_from_rml_key(rml_key);
|
||||
if (action != MenuAction::None) {
|
||||
handle_event(Event::menu_action_event(action));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Rml::EventId::Drag:
|
||||
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Move));
|
||||
break;
|
||||
@@ -288,6 +754,14 @@ void Element::ProcessEvent(Rml::Event &event) {
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_as_root_document(NavigationType nav_type) {
|
||||
is_root_document = true;
|
||||
is_nav_container = true;
|
||||
this->nav_type = nav_type;
|
||||
|
||||
register_event_listeners(recompui::Events(recompui::EventType::Navigate, recompui::EventType::Focus));
|
||||
}
|
||||
|
||||
void Element::set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value) {
|
||||
base->SetAttribute(attribute_key, attribute_value);
|
||||
}
|
||||
@@ -405,6 +879,15 @@ void Element::set_text(std::string_view text) {
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_text_unsafe(std::string_view text) {
|
||||
if (can_set_text) {
|
||||
get_current_context().queue_set_text(this, std::string(text));
|
||||
}
|
||||
else {
|
||||
assert(false && "Attempted to set text of an element that cannot have its text set.");
|
||||
}
|
||||
}
|
||||
|
||||
std::string Element::get_input_text() {
|
||||
return base->GetAttribute("value", std::string{});
|
||||
}
|
||||
@@ -573,4 +1056,15 @@ bool Element::is_pseudo_class_set(Rml::String pseudo_class) {
|
||||
return base->IsPseudoClassSet(pseudo_class);
|
||||
}
|
||||
|
||||
void Element::scroll_into_view() {
|
||||
if (base == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rml::ScrollIntoViewOptions options;
|
||||
options.vertical = Rml::ScrollAlignment::Nearest;
|
||||
|
||||
base->ScrollIntoView(options);
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
|
||||
@@ -18,6 +18,14 @@ struct UICallback {
|
||||
|
||||
using ElementValue = std::variant<uint32_t, float, double, std::monostate>;
|
||||
|
||||
enum class NavigationType {
|
||||
Auto,
|
||||
Vertical,
|
||||
Horizontal,
|
||||
GridCol,
|
||||
GridRow
|
||||
};
|
||||
|
||||
class ContextId;
|
||||
class Element : public Style, public Rml::EventListener {
|
||||
friend ContextId create_context(const std::filesystem::path& path);
|
||||
@@ -40,6 +48,13 @@ private:
|
||||
bool disabled_attribute = false;
|
||||
bool disabled_from_parent = false;
|
||||
bool can_set_text = false;
|
||||
bool is_root_document = false;
|
||||
|
||||
bool is_nav_container = false;
|
||||
NavigationType nav_type = NavigationType::Auto;
|
||||
bool is_primary_focus = false;
|
||||
|
||||
std::vector<Element *> nav_children;
|
||||
|
||||
void add_child(Element *child);
|
||||
void register_event_listeners(uint32_t events_enabled);
|
||||
@@ -47,12 +62,38 @@ private:
|
||||
void propagate_disabled(bool disabled);
|
||||
void handle_event(const Event &e);
|
||||
void set_id(const std::string& new_id);
|
||||
void set_as_root_document(NavigationType nav_type = NavigationType::Vertical);
|
||||
Element *try_grid_navigation(
|
||||
int nav_dir,
|
||||
int cur_element_index
|
||||
);
|
||||
Element *try_get_nav_direction(
|
||||
int vertical_nav,
|
||||
int horizontal_nav,
|
||||
Element *cur_nav_child
|
||||
);
|
||||
|
||||
// Style overrides.
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property) override;
|
||||
|
||||
bool handle_navigation_event(Rml::Event &event);
|
||||
|
||||
// Rml::EventListener overrides.
|
||||
void ProcessEvent(Rml::Event &event) override final;
|
||||
|
||||
Element *get_nav_parent();
|
||||
void get_all_focusable_children(Element *nav_parent);
|
||||
void build_navigation(Element *nav_parent, Element *cur_focus_element);
|
||||
void print_nav_hierarchy(int depth) {
|
||||
for (auto &child : nav_children) {
|
||||
for (int i = 0; i <= depth; i++) {
|
||||
printf(" ");
|
||||
}
|
||||
printf("%s\n", child->get_id().c_str());
|
||||
child->print_nav_hierarchy(depth + 1);
|
||||
}
|
||||
}
|
||||
Element *get_closest_element(std::vector<Element *> &elements);
|
||||
protected:
|
||||
// Use of this method in inherited classes is discouraged unless it's necessary.
|
||||
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
|
||||
@@ -71,12 +112,15 @@ public:
|
||||
bool remove_child(ResourceId child, bool remove_from_context = true);
|
||||
bool remove_child(Element *child, bool remove_from_context = true) { return remove_child(child->get_resource_id(), remove_from_context); }
|
||||
void set_parent(Element *new_parent);
|
||||
Element *get_parent() const { return parent; }
|
||||
void add_style(Style *style, std::string_view style_name);
|
||||
void add_style(Style *style, const std::initializer_list<std::string_view> &style_names);
|
||||
Element get_element_with_tag_name(std::string_view tag_name);
|
||||
void set_enabled(bool enabled);
|
||||
bool is_enabled() const;
|
||||
void set_text(std::string_view text);
|
||||
// Only use if you can ensure text is directly from a trusted source. Sets inner rml contents which can create new elements.
|
||||
void set_text_unsafe(std::string_view text);
|
||||
std::string get_input_text();
|
||||
void set_input_text(std::string_view text);
|
||||
void set_src(std::string_view src);
|
||||
@@ -103,9 +147,18 @@ public:
|
||||
void set_input_value_double(double val) { set_input_value(val); }
|
||||
const std::string& get_id() { return id; }
|
||||
bool is_pseudo_class_set(Rml::String pseudo_class);
|
||||
void scroll_into_view();
|
||||
|
||||
void set_as_navigation_container(NavigationType nav_type);
|
||||
void set_as_primary_focus(bool is_primary_focus = true);
|
||||
|
||||
Element *select_add_option(std::string_view text, std::string_view value);
|
||||
void select_set_selection(std::string_view option_value);
|
||||
std::vector<Element *> *get_nav_children() { return &nav_children; }
|
||||
|
||||
enum class CanFocus { No, Yes, NoAndNoChildren };
|
||||
CanFocus is_focusable();
|
||||
Element *get_first_focusable_child();
|
||||
};
|
||||
|
||||
void queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ui_icon_button.h"
|
||||
#include "ui_pseudo_border.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -12,6 +13,7 @@ namespace recompui {
|
||||
enable_focus();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::Center);
|
||||
set_width(float_size_internal);
|
||||
@@ -58,6 +60,9 @@ namespace recompui {
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
auto focus_border = context.create_element<FocusBorder>(this, true);
|
||||
focus_border->set_border_radius(static_cast<float>(size) + theme::border::width * 4.0f);
|
||||
|
||||
svg = context.create_element<Svg>(this, svg_src);
|
||||
svg->set_width(icon_size);
|
||||
svg->set_color(theme::color::TextDim);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ui_label.h"
|
||||
#include "ui_theme.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -7,36 +8,30 @@ namespace recompui {
|
||||
Label::Label(Element *parent, LabelStyle label_style) : Element(parent, 0U, "div", true) {
|
||||
switch (label_style) {
|
||||
case LabelStyle::Annotation:
|
||||
set_font_size(18.0f);
|
||||
set_letter_spacing(2.52f);
|
||||
set_line_height(18.0f);
|
||||
set_font_weight(400);
|
||||
set_typography(theme::Typography::LabelXS);
|
||||
break;
|
||||
case LabelStyle::Small:
|
||||
set_font_size(20.0f);
|
||||
set_letter_spacing(0.0f);
|
||||
set_line_height(20.0f);
|
||||
set_font_weight(400);
|
||||
set_typography(theme::Typography::Body);
|
||||
break;
|
||||
case LabelStyle::Normal:
|
||||
set_font_size(28.0f);
|
||||
set_letter_spacing(3.08f);
|
||||
set_line_height(28.0f);
|
||||
set_font_weight(700);
|
||||
set_typography(theme::Typography::LabelMD);
|
||||
break;
|
||||
case LabelStyle::Large:
|
||||
set_font_size(36.0f);
|
||||
set_letter_spacing(2.52f);
|
||||
set_line_height(36.0f);
|
||||
set_font_weight(700);
|
||||
set_typography(theme::Typography::Header3);
|
||||
break;
|
||||
}
|
||||
|
||||
set_font_style(FontStyle::Normal);
|
||||
}
|
||||
|
||||
Label::Label(Element *parent, const std::string &text, LabelStyle label_style) : Label(parent, label_style) {
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
Label::Label(Element *parent, theme::Typography typography) : Element(parent, 0U, "div", true) {
|
||||
set_typography(typography);
|
||||
}
|
||||
|
||||
Label::Label(Element *parent, const std::string &text, theme::Typography typography) : Label(parent, typography) {
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace recompui {
|
||||
public:
|
||||
Label(Element *parent, LabelStyle label_style);
|
||||
Label(Element *parent, const std::string &text, LabelStyle label_style);
|
||||
Label(Element *parent, theme::Typography typography);
|
||||
Label(Element *parent, const std::string &text, theme::Typography typography);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
} // namespace recompui
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
#include "ui_modal.h"
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ModalOverlay : public Element {
|
||||
protected:
|
||||
Modal *parent_modal;
|
||||
|
||||
void process_event(const Event &e) override {
|
||||
if (e.type != EventType::Click) {
|
||||
return;
|
||||
}
|
||||
|
||||
// parent_modal->close();
|
||||
}
|
||||
std::string_view get_type_name() override { return "ModalOverlay"; }
|
||||
public:
|
||||
ModalOverlay(Modal *parent) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "div", false) {
|
||||
this->parent_modal = parent;
|
||||
|
||||
set_display(Display::Block);
|
||||
set_position(Position::Absolute);
|
||||
set_top(0);
|
||||
set_right(0);
|
||||
set_bottom(0);
|
||||
set_left(0);
|
||||
set_background_color(theme::color::BGOverlay);
|
||||
}
|
||||
|
||||
~ModalOverlay() {
|
||||
}
|
||||
};
|
||||
|
||||
constexpr float modal_page_padding = 64.0f;
|
||||
constexpr float modal_page_base_height = 1080.0f;
|
||||
constexpr float modal_height = modal_page_base_height - (modal_page_padding * 2.0f);
|
||||
constexpr float modal_width = modal_page_base_height * 16.0f / 9.0f;
|
||||
|
||||
Modal::Modal(
|
||||
Element *parent,
|
||||
recompui::ContextId modal_root_context,
|
||||
ModalType modal_type
|
||||
) : Element(parent, Events(EventType::MenuAction), "div", false)
|
||||
{
|
||||
this->modal_root_context = modal_root_context;
|
||||
this->modal_type = modal_type;
|
||||
recompui::ContextId context = modal_root_context;
|
||||
|
||||
set_display(Display::None);
|
||||
set_position(Position::Absolute);
|
||||
set_top(0);
|
||||
set_right(0);
|
||||
set_bottom(0);
|
||||
set_left(0);
|
||||
|
||||
ModalOverlay* modal_overlay = context.create_element<ModalOverlay>(this);
|
||||
|
||||
Element* modal_whole_page_wrapper = context.create_element<Element>(modal_overlay);
|
||||
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_padding(modal_page_padding);
|
||||
modal_whole_page_wrapper->set_align_items(AlignItems::Center);
|
||||
modal_whole_page_wrapper->set_justify_content(JustifyContent::Center);
|
||||
|
||||
modal_element = context.create_element<Element>(modal_whole_page_wrapper);
|
||||
modal_element->set_display(Display::Flex);
|
||||
modal_element->set_position(Position::Relative);
|
||||
modal_element->set_flex(1.0f, 1.0f);
|
||||
modal_element->set_flex_basis(100, Unit::Percent);
|
||||
modal_element->set_flex_direction(FlexDirection::Column);
|
||||
modal_element->set_width(100, Unit::Percent);
|
||||
modal_element->set_max_width(modal_width);
|
||||
modal_element->set_height(100, Unit::Percent);
|
||||
modal_element->set_margin_auto();
|
||||
modal_element->set_border_width(theme::border::width);
|
||||
modal_element->set_border_radius(theme::border::radius_lg);
|
||||
modal_element->set_border_color(theme::color::Border);
|
||||
modal_element->set_background_color(theme::color::ModalOverlay);
|
||||
|
||||
header = context.create_element<ConfigHeaderFooter>(modal_element, true);
|
||||
header->set_padding_top(0.0f);
|
||||
header->set_padding_bottom(0.0f);
|
||||
header->set_padding_left(8.0f);
|
||||
header->set_padding_right(8.0f);
|
||||
header->set_border_top_left_radius(theme::border::radius_lg);
|
||||
header->set_border_top_right_radius(theme::border::radius_lg);
|
||||
|
||||
body = context.create_element<Element>(modal_element);
|
||||
body->set_display(Display::Flex);
|
||||
body->set_position(Position::Relative);
|
||||
body->set_flex_grow(1.0f);
|
||||
body->set_flex_shrink(1.0f);
|
||||
body->set_flex_basis_auto();
|
||||
body->set_flex_direction(FlexDirection::Row);
|
||||
body->set_width(100.0f, Unit::Percent);
|
||||
}
|
||||
|
||||
Modal::~Modal() {
|
||||
}
|
||||
|
||||
void Modal::open() {
|
||||
if (!recompui::is_context_shown(modal_root_context)) {
|
||||
recompui::show_context(modal_root_context, "");
|
||||
}
|
||||
|
||||
is_open = true;
|
||||
set_display(Display::Block);
|
||||
// queue_update();
|
||||
if (tabs != nullptr) {
|
||||
tabs->focus_on_active_tab();
|
||||
on_tab_change(tabs->get_active_tab());
|
||||
}
|
||||
}
|
||||
|
||||
void Modal::close() {
|
||||
if (current_tab_index < tab_contexts.size() && current_tab_index >= 0) {
|
||||
if (tab_contexts[current_tab_index]->can_be_closed()) {
|
||||
tab_contexts[current_tab_index]->on_tab_close();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (recompui::is_context_shown(modal_root_context)) {
|
||||
set_display(Display::None);
|
||||
recompui::hide_context(modal_root_context);
|
||||
}
|
||||
|
||||
is_open = false;
|
||||
|
||||
if (on_close_callback != nullptr) {
|
||||
on_close_callback();
|
||||
}
|
||||
}
|
||||
|
||||
void Modal::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Update: {
|
||||
if (previous_tab_index != current_tab_index) {
|
||||
body->clear_children();
|
||||
Element* tab_contents = tab_contexts[current_tab_index]->create_tab_contents(modal_root_context, body);
|
||||
previous_tab_index = current_tab_index;
|
||||
}
|
||||
queue_update();
|
||||
break;
|
||||
}
|
||||
case EventType::MenuAction: {
|
||||
auto action = std::get<EventMenuAction>(e.variant).action;
|
||||
switch (action) {
|
||||
case MenuAction::Accept:
|
||||
break;
|
||||
case MenuAction::Apply:
|
||||
break;
|
||||
case MenuAction::Back: {
|
||||
if (tabs != nullptr && tabs->get_active_tab_element() != nullptr) {
|
||||
tabs->get_active_tab_element()->focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MenuAction::Toggle: {
|
||||
close();
|
||||
break;
|
||||
}
|
||||
case MenuAction::TabLeft: {
|
||||
navigate_tab_direction(-1);
|
||||
break;
|
||||
}
|
||||
case MenuAction::TabRight: {
|
||||
navigate_tab_direction(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Modal::navigate_tab_direction(int direction) {
|
||||
if (tab_contexts.size() == 0) {
|
||||
return;
|
||||
}
|
||||
int next_index = current_tab_index + direction;
|
||||
if (next_index < 0) {
|
||||
next_index = tab_contexts.size() - 1;
|
||||
} else if (next_index >= tab_contexts.size()) {
|
||||
next_index = 0;
|
||||
}
|
||||
set_selected_tab(next_index);
|
||||
if (tabs != nullptr && tabs->get_active_tab_element() != nullptr) {
|
||||
tabs->get_active_tab_element()->focus();
|
||||
}
|
||||
}
|
||||
|
||||
void Modal::set_menu_action_callback(MenuAction action, std::function<void()> callback) {
|
||||
menu_action_callbacks[action] = callback;
|
||||
}
|
||||
|
||||
void Modal::on_tab_change(int tab_index) {
|
||||
if (current_tab_index < tab_contexts.size() && current_tab_index >= 0) {
|
||||
if (tab_contexts[current_tab_index]->can_be_closed()) {
|
||||
tab_contexts[current_tab_index]->on_tab_close();
|
||||
current_tab_index = tab_index;
|
||||
} else if (tabs != nullptr) {
|
||||
tabs->set_active_tab(current_tab_index);
|
||||
}
|
||||
} else {
|
||||
current_tab_index = tab_index;
|
||||
}
|
||||
|
||||
if (current_tab_index < tab_contexts.size() && current_tab_index >= 0 && tabs != nullptr) {
|
||||
tab_contexts[current_tab_index]->set_nav_up(tabs->get_active_tab_element());
|
||||
}
|
||||
|
||||
// body->clear_children();
|
||||
// Element* tab_contents = tab_contexts[tab_index]->create_tab_contents(modal_root_context, body);
|
||||
}
|
||||
|
||||
void Modal::set_selected_tab(int tab_index) {
|
||||
if (tabs != nullptr) {
|
||||
// calls Modal::on_tab_change internally
|
||||
tabs->set_active_tab(tab_index);
|
||||
}
|
||||
}
|
||||
|
||||
void Modal::add_tab(TabContext *tab_context) {
|
||||
tab_context->set_parent_modal(this);
|
||||
tab_context->on_init();
|
||||
tab_contexts.push_back(tab_context);
|
||||
ContextId context = get_current_context();
|
||||
if (tabs == nullptr) {
|
||||
header->set_padding_left(0.0f); // tabs hug to left side
|
||||
tabs = context.create_element<TabSet>(header->get_left());
|
||||
tabs->set_change_tab_callback([this](int tab_index) {
|
||||
this->on_tab_change(tab_index);
|
||||
});
|
||||
}
|
||||
tabs->add_tab(tab_context->tab_name);
|
||||
}
|
||||
|
||||
Modal *Modal::create_modal(ModalType modal_type) {
|
||||
ContextId new_context = recompui::create_context();
|
||||
new_context.open();
|
||||
auto doc = new_context.get_root_element();
|
||||
Modal *modal = new_context.create_element<Modal>(doc, new_context, modal_type);
|
||||
new_context.close();
|
||||
return modal;
|
||||
}
|
||||
|
||||
void Modal::set_first_focusable_below_tabs(Element *element) {
|
||||
if (tabs != nullptr) {
|
||||
tabs->set_nav_down(element);
|
||||
}
|
||||
}
|
||||
|
||||
void Modal::set_on_close_callback(std::function<void()> callback) {
|
||||
on_close_callback = callback;
|
||||
}
|
||||
|
||||
void TabContext::apply_set_first_focusable_below_tabs_callback() {
|
||||
set_first_focusable_below_tabs = [this](Element *element) {
|
||||
if (this->parent_modal != nullptr) {
|
||||
this->parent_modal->set_first_focusable_below_tabs(element);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "ui_element.h"
|
||||
#include "ui_config_page.h"
|
||||
#include "ui_tab_set.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Modal;
|
||||
|
||||
enum class ModalType {
|
||||
Fullscreen,
|
||||
Prompt
|
||||
};
|
||||
|
||||
// Function in Modal::set_first_focusable_below_tabs, reports the element to focus on when navigating down from the tabs
|
||||
using set_first_focusable_below_tabs_t = std::function<void(Element*)>;
|
||||
|
||||
class TabContext {
|
||||
private:
|
||||
Element *upper_focusable = nullptr;
|
||||
Modal *parent_modal = nullptr;
|
||||
protected:
|
||||
set_first_focusable_below_tabs_t set_first_focusable_below_tabs = nullptr;
|
||||
public:
|
||||
std::string tab_name;
|
||||
TabContext(std::string name) : tab_name(name) {};
|
||||
virtual ~TabContext() = default;
|
||||
virtual Element* create_tab_contents(recompui::ContextId context, Element* parent) = 0;
|
||||
virtual bool can_be_closed() { return true; }
|
||||
virtual void on_tab_close() {}
|
||||
virtual void on_init() {}
|
||||
|
||||
virtual void set_nav_up(Element *element) { upper_focusable = element; }
|
||||
Element* get_nav_up() const { return upper_focusable; }
|
||||
Modal *get_parent_modal() const { return parent_modal; }
|
||||
void set_parent_modal(Modal *modal) { parent_modal = modal; }
|
||||
|
||||
void apply_set_first_focusable_below_tabs_callback();
|
||||
};
|
||||
|
||||
class Modal : public Element {
|
||||
protected:
|
||||
bool is_open = false;
|
||||
Element *modal_element = nullptr;
|
||||
ConfigHeaderFooter *header = nullptr;
|
||||
Element *body = nullptr;
|
||||
ModalType modal_type;
|
||||
std::vector<TabContext*> tab_contexts;
|
||||
std::function<void()> on_close_callback;
|
||||
std::unordered_map<MenuAction, std::function<void()>> menu_action_callbacks;
|
||||
TabSet *tabs = nullptr;
|
||||
int previous_tab_index = -1;
|
||||
int current_tab_index = -1;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Modal"; }
|
||||
void on_tab_change(int tab_index);
|
||||
void navigate_tab_direction(int direction);
|
||||
public:
|
||||
recompui::ContextId modal_root_context;
|
||||
Modal(Element *parent, recompui::ContextId modal_root_context, ModalType modal_type);
|
||||
static Modal *create_modal(ModalType modal_type = ModalType::Fullscreen);
|
||||
virtual ~Modal();
|
||||
void open();
|
||||
void close();
|
||||
bool is_open_now() const { return is_open; }
|
||||
|
||||
void add_tab(TabContext *tab_context);
|
||||
void set_selected_tab(int tab_index);
|
||||
|
||||
ConfigHeaderFooter *get_header() { return header; }
|
||||
Element *get_body() { return body; }
|
||||
Element *get_active_tab() { return tabs != nullptr ? static_cast<Tab*>(tabs->get_active_tab_element()) : nullptr; }
|
||||
// set_first_focusable_below_tabs_t
|
||||
void set_first_focusable_below_tabs(Element *element);
|
||||
void set_on_close_callback(std::function<void()> callback);
|
||||
void set_menu_action_callback(MenuAction action, std::function<void()> callback);
|
||||
};
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "ui_pseudo_border.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
PseudoBorder::PseudoBorder(Element *parent, bool inset, float border_width, float border_outset_distance) : Element(parent) {
|
||||
if (border_width > 0) {
|
||||
set_border_width(border_width);
|
||||
} else {
|
||||
set_border_width(theme::border::width);
|
||||
border_width = theme::border::width;
|
||||
}
|
||||
|
||||
set_position(Position::Absolute);
|
||||
if (inset) {
|
||||
set_inset(border_width);
|
||||
} else {
|
||||
set_inset(-(border_width + border_outset_distance));
|
||||
}
|
||||
}
|
||||
|
||||
FocusBorder::FocusBorder(Element *parent, bool extra_dist) : PseudoBorder(parent, false, theme::border::width * 2.0f, theme::border::width * 2.0f * (extra_dist ? 2.0f : 1.0f)) {
|
||||
set_border_color(theme::color::PrimaryL, 0);
|
||||
}
|
||||
|
||||
void FocusBorder::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Update: {
|
||||
if (get_parent()->is_style_enabled(focus_state)) {
|
||||
set_border_color(recompui::get_pulse_color(750));
|
||||
} else {
|
||||
set_border_color(theme::color::PrimaryL, 0);
|
||||
}
|
||||
|
||||
queue_update();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class PseudoBorder : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "PseudoBorder"; }
|
||||
public:
|
||||
PseudoBorder(Element *parent, bool inset = true, float border_width = 0, float border_outset_distance = 0);
|
||||
};
|
||||
|
||||
class FocusBorder : public PseudoBorder {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "FocusBorder"; }
|
||||
virtual void process_event(const Event &e) override;
|
||||
public:
|
||||
FocusBorder(Element *parent, bool extra_dist = false);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -12,25 +12,27 @@ namespace recompui {
|
||||
enable_focus();
|
||||
set_text(name);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_font_size(20.0f);
|
||||
set_letter_spacing(2.8f);
|
||||
set_line_height(20.0f);
|
||||
set_font_weight(400);
|
||||
set_font_style(FontStyle::Normal);
|
||||
set_typography(theme::Typography::LabelSM);
|
||||
set_border_color(theme::color::Text, 0);
|
||||
set_border_bottom_width(1.0f);
|
||||
set_border_bottom_width(theme::border::width);
|
||||
set_color(theme::color::TextInactive);
|
||||
set_padding_bottom(8.0f);
|
||||
set_padding_top(8.0f);
|
||||
set_padding_bottom(8.0f - theme::border::width);
|
||||
set_text_transform(TextTransform::Uppercase);
|
||||
set_height_auto();
|
||||
set_opacity(1.0f);
|
||||
hover_style.set_color(theme::color::WhiteA80);
|
||||
checked_style.set_color(theme::color::White);
|
||||
checked_style.set_border_color(theme::color::Text);
|
||||
pulsing_style.set_border_color(theme::color::SecondaryA80);
|
||||
disabled_style.set_color(theme::color::TextInactive);
|
||||
disabled_style.set_opacity(0.5f);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
add_style(&hover_style, { hover_state });
|
||||
add_style(&checked_style, { checked_state });
|
||||
add_style(&pulsing_style, { focus_state });
|
||||
add_style(&disabled_style, { disabled_state });
|
||||
}
|
||||
|
||||
void RadioOption::set_pressed_callback(std::function<void(uint32_t)> callback) {
|
||||
@@ -43,27 +45,30 @@ namespace recompui {
|
||||
|
||||
void RadioOption::set_selected_state(bool enable) {
|
||||
set_style_enabled(checked_state, enable);
|
||||
set_as_primary_focus(enable);
|
||||
}
|
||||
|
||||
void RadioOption::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::MouseButton:
|
||||
{
|
||||
const EventMouseButton &mousebutton = std::get<EventMouseButton>(e.variant);
|
||||
if (mousebutton.button == MouseButton::Left && mousebutton.pressed) {
|
||||
pressed_callback(index);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Click:
|
||||
pressed_callback(index);
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active);
|
||||
break;
|
||||
case EventType::Enable:
|
||||
set_style_enabled(disabled_state, !std::get<EventEnable>(e.variant).active);
|
||||
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);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool active = std::get<EventFocus>(e.variant).active;
|
||||
@@ -92,9 +97,13 @@ namespace recompui {
|
||||
|
||||
void Radio::set_index_internal(uint32_t index, bool setup, bool trigger_callbacks) {
|
||||
if (this->index != index || setup) {
|
||||
options[this->index]->set_selected_state(false);
|
||||
if (this->index < static_cast<uint32_t>(options.size())) {
|
||||
options[this->index]->set_selected_state(false);
|
||||
}
|
||||
if (index < static_cast<uint32_t>(options.size())) {
|
||||
options[index]->set_selected_state(true);
|
||||
}
|
||||
this->index = index;
|
||||
options[index]->set_selected_state(true);
|
||||
|
||||
if (trigger_callbacks) {
|
||||
for (const auto &function : index_changed_callbacks) {
|
||||
@@ -117,20 +126,21 @@ namespace recompui {
|
||||
}, val);
|
||||
}
|
||||
|
||||
Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart, Events(EventType::Focus, EventType::Update)) {
|
||||
Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart, Events(EventType::Focus, EventType::Update, EventType::Enable)) {
|
||||
set_gap(24.0f);
|
||||
set_align_items(AlignItems::FlexStart);
|
||||
enable_focus();
|
||||
// enable_focus();
|
||||
set_as_navigation_container(NavigationType::Horizontal);
|
||||
}
|
||||
|
||||
void Radio::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Focus:
|
||||
if (!options.empty()) {
|
||||
if (std::get<EventFocus>(e.variant).active) {
|
||||
blur();
|
||||
queue_child_focus();
|
||||
}
|
||||
// if (std::get<EventFocus>(e.variant).active) {
|
||||
// blur();
|
||||
// queue_child_focus();
|
||||
// }
|
||||
if (focus_callback != nullptr) {
|
||||
focus_callback(std::get<EventFocus>(e.variant).active);
|
||||
}
|
||||
@@ -139,8 +149,21 @@ namespace recompui {
|
||||
case EventType::Update:
|
||||
if (child_focus_queued) {
|
||||
child_focus_queued = false;
|
||||
options[index]->focus();
|
||||
if (index < static_cast<uint32_t>(options.size())) {
|
||||
options[index]->focus();
|
||||
} else if (!options.empty()) {
|
||||
options.front()->focus();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool active = std::get<EventEnable>(e.variant).active;
|
||||
for (auto &option : options) {
|
||||
option->set_enabled(active);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace recompui {
|
||||
Style hover_style;
|
||||
Style checked_style;
|
||||
Style pulsing_style;
|
||||
Style disabled_style;
|
||||
std::function<void(uint32_t)> pressed_callback = nullptr;
|
||||
std::function<void(bool)> focus_callback = nullptr;
|
||||
uint32_t index = 0;
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace recompui {
|
||||
char text_buffer[32];
|
||||
|
||||
if (type == SliderType::Double) {
|
||||
std::snprintf(text_buffer, sizeof(text_buffer), "%.1f", value);
|
||||
std::snprintf(text_buffer, sizeof(text_buffer), "%.*f", precision, value);
|
||||
} else if (type == SliderType::Percent) {
|
||||
std::snprintf(text_buffer, sizeof(text_buffer), "%d%%", static_cast<int>(value));
|
||||
} else {
|
||||
@@ -114,7 +114,8 @@ namespace recompui {
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
circle_element->set_enabled(enable_active);
|
||||
// circle_element->set_enabled(enable_active);
|
||||
// slider_element->set_enabled(enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
@@ -217,6 +218,14 @@ namespace recompui {
|
||||
double Slider::get_step_value() const {
|
||||
return step_value;
|
||||
}
|
||||
|
||||
void Slider::set_precision(int p) {
|
||||
precision = p;
|
||||
}
|
||||
|
||||
int Slider::get_precision() const {
|
||||
return precision;
|
||||
}
|
||||
|
||||
void Slider::add_value_changed_callback(std::function<void(double)> callback) {
|
||||
value_changed_callbacks.emplace_back(callback);
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace recompui {
|
||||
double min_value = 0.0;
|
||||
double max_value = 100.0;
|
||||
double step_value = 0.0;
|
||||
int precision = 0;
|
||||
std::vector<std::function<void(double)>> value_changed_callbacks;
|
||||
std::function<void(bool)> focus_callback = nullptr;
|
||||
|
||||
@@ -50,6 +51,8 @@ namespace recompui {
|
||||
double get_max_value() const;
|
||||
void set_step_value(double v);
|
||||
double get_step_value() const;
|
||||
void set_precision(int p);
|
||||
int get_precision() const;
|
||||
void add_value_changed_callback(std::function<void(double)> callback);
|
||||
void do_step(bool increment);
|
||||
void set_focus_callback(std::function<void(bool)> callback);
|
||||
|
||||
@@ -239,6 +239,13 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::Bottom, Rml::Property(bottom, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_inset(float inset, Unit unit) {
|
||||
set_top(inset, unit);
|
||||
set_left(inset, unit);
|
||||
set_right(inset, unit);
|
||||
set_bottom(inset, unit);
|
||||
}
|
||||
|
||||
void Style::set_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::Width, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
@@ -610,6 +617,15 @@ namespace recompui {
|
||||
set_property(Rml::PropertyId::FontWeight, Rml::Style::FontWeight(weight));
|
||||
}
|
||||
|
||||
void Style::set_typography(recompui::theme::Typography typography) {
|
||||
const auto &preset = recompui::theme::get_typography_preset(typography);
|
||||
set_font_size(preset.font_size);
|
||||
set_letter_spacing(preset.letter_spacing);
|
||||
set_line_height(preset.line_height);
|
||||
set_font_weight(preset.font_weight);
|
||||
set_font_style(preset.font_style);
|
||||
}
|
||||
|
||||
void Style::set_text_align(TextAlign text_align) {
|
||||
set_property(Rml::PropertyId::TextAlign, to_rml(text_align));
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace recompui {
|
||||
void set_top(float top, Unit unit = Unit::Dp);
|
||||
void set_right(float right, Unit unit = Unit::Dp);
|
||||
void set_bottom(float bottom, Unit unit = Unit::Dp);
|
||||
void set_inset(float inset, Unit unit = Unit::Dp);
|
||||
void set_width(float width, Unit unit = Unit::Dp);
|
||||
void set_width_auto();
|
||||
void set_height(float height, Unit unit = Unit::Dp);
|
||||
@@ -101,6 +102,7 @@ namespace recompui {
|
||||
void set_line_height(float height, Unit unit = Unit::Dp);
|
||||
void set_font_style(FontStyle style);
|
||||
void set_font_weight(uint32_t weight);
|
||||
void set_typography(recompui::theme::Typography typography);
|
||||
void set_text_align(TextAlign text_align);
|
||||
void set_text_transform(TextTransform text_transform);
|
||||
void set_gap(float size, Unit unit = Unit::Dp);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
#include "ui_tab_set.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Tab::Tab(Element *parent, int tab_index, std::string_view text, on_change_tab_callback on_select_tab) :
|
||||
Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button", false)
|
||||
{
|
||||
this->tab_index = tab_index;
|
||||
this->on_select_tab = std::move(on_select_tab);
|
||||
set_display(Display::Block);
|
||||
set_position(Position::Relative);
|
||||
set_margin(0);
|
||||
set_padding_top(tab_vertical_padding);
|
||||
set_padding_bottom(tab_vertical_padding);
|
||||
set_padding_left(tab_horizontal_padding);
|
||||
set_padding_right(tab_horizontal_padding);
|
||||
set_focusable(true);
|
||||
set_tab_index_auto();
|
||||
set_nav_none(NavDirection::Left);
|
||||
set_nav_none(NavDirection::Right);
|
||||
set_nav_auto(NavDirection::Up);
|
||||
set_nav_auto(NavDirection::Down);
|
||||
|
||||
set_color(theme::color::TextInactive);
|
||||
set_background_color(theme::color::Transparent);
|
||||
|
||||
hover_style.set_color(theme::color::WhiteA80);
|
||||
checked_style.set_color(theme::color::White);
|
||||
pulsing_style.set_color(theme::color::WhiteA80);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
label = context.create_element<Label>(this, std::string{text}, theme::Typography::Header3);
|
||||
|
||||
indicator = context.create_element<Element>(this, 0, "div", false);
|
||||
indicator->set_display(Display::Block);
|
||||
indicator->set_position(Position::Absolute);
|
||||
indicator->set_height(2.0f);
|
||||
indicator->set_bottom(2.0f);
|
||||
indicator->set_left(0.0f);
|
||||
indicator->set_right(0.0f);
|
||||
indicator->set_background_color(theme::color::Transparent);
|
||||
|
||||
add_style(&hover_style, { hover_state });
|
||||
add_style(&checked_style, { checked_state });
|
||||
add_style(&pulsing_style, { focus_state });
|
||||
}
|
||||
|
||||
void Tab::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
on_select_tab(tab_index);
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active);
|
||||
break;
|
||||
case EventType::Enable: {
|
||||
bool active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !active);
|
||||
indicator->set_background_color(active ? theme::color::BorderSolid : theme::color::Transparent);
|
||||
break;
|
||||
}
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, active);
|
||||
if (active) {
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
if (is_style_enabled(focus_state)) {
|
||||
recompui::Color pulse_color = recompui::get_pulse_color(750);
|
||||
pulsing_style.set_color(pulse_color);
|
||||
if (is_selected) {
|
||||
indicator->set_background_color(pulse_color);
|
||||
} else {
|
||||
indicator->set_background_color(theme::color::Transparent);
|
||||
}
|
||||
apply_styles();
|
||||
queue_update();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Tab::set_selected(bool enable) {
|
||||
is_selected = enable;
|
||||
set_as_primary_focus(enable);
|
||||
set_style_enabled(checked_state, enable);
|
||||
indicator->set_background_color(enable ? theme::color::BorderSolid : theme::color::Transparent);
|
||||
}
|
||||
|
||||
TabSet::TabSet(Element *parent) :
|
||||
Element(parent, 0, "div", false)
|
||||
{
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_justify_content(JustifyContent::FlexStart);
|
||||
set_align_items(AlignItems::Stretch);
|
||||
set_gap(0.0f);
|
||||
set_as_navigation_container(NavigationType::Horizontal);
|
||||
}
|
||||
|
||||
void TabSet::set_change_tab_callback(on_change_tab_callback callback) {
|
||||
change_tab_callback = std::move(callback);
|
||||
}
|
||||
|
||||
int TabSet::add_tab(std::string_view text) {
|
||||
ContextId context = get_current_context();
|
||||
Tab *tab = context.create_element<Tab>(this, tabs.size(), text, [this](int tab_index) {
|
||||
set_active_tab(tab_index);
|
||||
});
|
||||
tabs.push_back(tab);
|
||||
if (active_tab < 0) {
|
||||
active_tab = 0;
|
||||
tab->set_selected(true);
|
||||
} else {
|
||||
tab->set_selected(active_tab == tabs.size() - 1);
|
||||
}
|
||||
|
||||
if (tabs.size() > 1) {
|
||||
tabs[tabs.size() - 2]->set_nav(NavDirection::Right, tab);
|
||||
tab->set_nav(NavDirection::Left, tabs[tabs.size() - 2]);
|
||||
tab->set_nav_none(NavDirection::Right);
|
||||
}
|
||||
|
||||
return tabs.size() - 1;
|
||||
}
|
||||
|
||||
void TabSet::set_active_tab(int tab_index) {
|
||||
if (tab_index < 0 || tab_index >= static_cast<int>(tabs.size())) {
|
||||
return;
|
||||
}
|
||||
if (active_tab != tab_index) {
|
||||
active_tab = tab_index;
|
||||
for (int i = 0; i < static_cast<int>(tabs.size()); i++) {
|
||||
tabs[i]->set_selected(i == active_tab);
|
||||
}
|
||||
if (change_tab_callback) {
|
||||
change_tab_callback(active_tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabSet::focus_on_active_tab() {
|
||||
if (active_tab >= 0 && active_tab < static_cast<int>(tabs.size())) {
|
||||
tabs[active_tab]->focus();
|
||||
}
|
||||
};
|
||||
|
||||
void TabSet::set_nav_down(Element *element) {
|
||||
for (auto &tab : tabs) {
|
||||
tab->set_nav(NavDirection::Down, element);
|
||||
}
|
||||
}
|
||||
|
||||
void TabSet::set_nav_up(Element *element) {
|
||||
for (auto &tab : tabs) {
|
||||
tab->set_nav(NavDirection::Up, element);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
using on_change_tab_callback = std::function<void(int tab_index)>;
|
||||
|
||||
|
||||
class Tab : public Element {
|
||||
private:
|
||||
Style hover_style;
|
||||
Style checked_style;
|
||||
Style pulsing_style;
|
||||
int tab_index;
|
||||
bool is_selected = false;
|
||||
on_change_tab_callback on_select_tab;
|
||||
Element *label = nullptr;
|
||||
Element *indicator = nullptr;
|
||||
|
||||
static constexpr float tab_vertical_padding = 20.0f;
|
||||
static constexpr float tab_horizontal_padding = 24.0f;
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Tab"; }
|
||||
virtual void process_event(const Event &e) override;
|
||||
public:
|
||||
Tab(Element *parent, int tab_index, std::string_view text, on_change_tab_callback on_select_tab);
|
||||
void set_selected(bool selected);
|
||||
};
|
||||
|
||||
class TabSet : public Element {
|
||||
private:
|
||||
std::vector<Tab *> tabs;
|
||||
on_change_tab_callback change_tab_callback;
|
||||
int active_tab = -1;
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "TabSet"; }
|
||||
public:
|
||||
TabSet(Element *parent);
|
||||
void set_change_tab_callback(on_change_tab_callback callback);
|
||||
int add_tab(std::string_view text);
|
||||
void set_active_tab(int tab_index);
|
||||
void focus_on_active_tab();
|
||||
Element *get_active_tab_element() const { return active_tab >= 0 && active_tab < static_cast<int>(tabs.size()) ? tabs[active_tab] : nullptr; };
|
||||
void set_nav_up(Element *element);
|
||||
void set_nav_down(Element *element);
|
||||
int get_active_tab() const { return active_tab; }
|
||||
};
|
||||
|
||||
|
||||
} // namespace recompui
|
||||
@@ -21,6 +21,7 @@ namespace recompui {
|
||||
if (focus_callback != nullptr) {
|
||||
focus_callback(event.active);
|
||||
}
|
||||
set_style_enabled(focus_state, event.active);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -33,13 +34,16 @@ namespace recompui {
|
||||
set_attribute("type", "password");
|
||||
}
|
||||
set_min_width(60.0f);
|
||||
set_border_color(theme::color::Text);
|
||||
set_border_bottom_width(1.0f);
|
||||
set_padding_bottom(6.0f);
|
||||
set_border_color(theme::color::TextA50);
|
||||
set_border_width(theme::border::width);
|
||||
set_padding(8.0f - theme::border::width);
|
||||
set_border_radius(theme::border::radius_sm);
|
||||
set_focusable(true);
|
||||
set_nav_auto(NavDirection::Up);
|
||||
set_nav_auto(NavDirection::Down);
|
||||
set_tab_index_auto();
|
||||
focus_style.set_border_color(theme::color::Primary);
|
||||
add_style(&focus_style, focus_state);
|
||||
}
|
||||
|
||||
void TextInput::set_text(std::string_view text) {
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace recompui {
|
||||
std::string text;
|
||||
std::vector<std::function<void(const std::string &)>> text_changed_callbacks;
|
||||
std::function<void(bool)> focus_callback = nullptr;
|
||||
Style focus_style;
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "TextInput"; }
|
||||
|
||||
@@ -227,3 +227,41 @@ const recompui::Color &recompui::theme::get_theme_color(recompui::theme::color c
|
||||
const char *recompui::theme::get_theme_color_name(recompui::theme::color color) {
|
||||
return theme_color_names[(std::size_t)color];
|
||||
}
|
||||
|
||||
using TypographyPreset = recompui::theme::TypographyPreset;
|
||||
|
||||
constexpr TypographyPreset create_typography_preset(float font_size, float letter_spacing_percentage, uint32_t font_weight = 400, recompui::FontStyle font_style = recompui::FontStyle::Normal) {
|
||||
TypographyPreset preset;
|
||||
preset.font_size = font_size;
|
||||
preset.line_height = font_size;
|
||||
preset.letter_spacing = font_size * letter_spacing_percentage;
|
||||
preset.font_weight = font_weight;
|
||||
preset.font_style = font_style;
|
||||
return preset;
|
||||
}
|
||||
|
||||
using TypographyArray = std::array<TypographyPreset, (std::size_t)(recompui::theme::Typography::size)>;
|
||||
constexpr TypographyArray get_default_typography_array() {
|
||||
TypographyArray typography = {};
|
||||
|
||||
typography[(std::size_t)recompui::theme::Typography::Header1] = create_typography_preset(68.0f, 0.07f, 700);
|
||||
typography[(std::size_t)recompui::theme::Typography::Header2] = create_typography_preset(52.0f, 0.07f, 700);
|
||||
typography[(std::size_t)recompui::theme::Typography::Header3] = create_typography_preset(36.0f, 0.07f, 700);
|
||||
typography[(std::size_t)recompui::theme::Typography::LabelLG] = create_typography_preset(36.0f, 0.11f, 700);
|
||||
typography[(std::size_t)recompui::theme::Typography::LabelMD] = create_typography_preset(28.0f, 0.11f, 700);
|
||||
typography[(std::size_t)recompui::theme::Typography::LabelSM] = create_typography_preset(20.0f, 0.14f, 700);
|
||||
typography[(std::size_t)recompui::theme::Typography::LabelXS] = create_typography_preset(18.0f, 0.14f);
|
||||
typography[(std::size_t)recompui::theme::Typography::Body] = create_typography_preset(20.0f, 0);
|
||||
|
||||
return typography;
|
||||
}
|
||||
|
||||
static TypographyArray typography_presets = get_default_typography_array();
|
||||
|
||||
void recompui::theme::set_typography_preset(Typography type, float font_size, float letter_spacing_percentage, uint32_t font_weight, recompui::FontStyle font_style) {
|
||||
typography_presets[(std::size_t)type] = create_typography_preset(font_size, letter_spacing_percentage, font_weight, font_style);
|
||||
}
|
||||
|
||||
TypographyPreset &recompui::theme::get_typography_preset(recompui::theme::Typography type) {
|
||||
return typography_presets[(std::size_t)type];
|
||||
}
|
||||
|
||||
@@ -98,6 +98,10 @@ namespace recompui {
|
||||
size,
|
||||
};
|
||||
|
||||
const char *get_theme_color_name(theme::color color);
|
||||
void set_theme_color(theme::color color, const recompui::Color &value);
|
||||
const recompui::Color &get_theme_color(theme::color color);
|
||||
|
||||
namespace border {
|
||||
extern float radius_sm;
|
||||
extern float radius_md;
|
||||
@@ -105,9 +109,29 @@ namespace recompui {
|
||||
extern float width;
|
||||
}
|
||||
|
||||
const char *get_theme_color_name(theme::color color);
|
||||
void set_theme_color(theme::color color, const recompui::Color &value);
|
||||
const recompui::Color &get_theme_color(theme::color color);
|
||||
enum class Typography {
|
||||
Header1,
|
||||
Header2,
|
||||
Header3,
|
||||
LabelLG,
|
||||
LabelMD,
|
||||
LabelSM,
|
||||
LabelXS,
|
||||
Body,
|
||||
|
||||
size,
|
||||
};
|
||||
|
||||
struct TypographyPreset {
|
||||
float font_size;
|
||||
float line_height;
|
||||
float letter_spacing;
|
||||
uint32_t font_weight = 400;
|
||||
recompui::FontStyle font_style = recompui::FontStyle::Normal;
|
||||
};
|
||||
|
||||
void set_typography_preset(Typography type, float font_size, float letter_spacing_percentage = 0.0f, uint32_t font_weight = 400, recompui::FontStyle font_style = recompui::FontStyle::Normal);
|
||||
TypographyPreset &get_typography_preset(Typography type);
|
||||
|
||||
} // namespace theme
|
||||
} // namespace recompui
|
||||
|
||||
@@ -1,38 +1,86 @@
|
||||
#include "ui_toggle.h"
|
||||
#include "ui_pseudo_border.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
namespace recompui {
|
||||
static constexpr float toggle_height = 72.0f;
|
||||
static constexpr float toggle_width = 162.0f;
|
||||
|
||||
static constexpr float toggle_floater_height = 64.0f;
|
||||
static constexpr float toggle_floater_width = 80.0f;
|
||||
struct ToggleSizing {
|
||||
float width;
|
||||
float height;
|
||||
float floater_width;
|
||||
float floater_height;
|
||||
float floater_margin;
|
||||
|
||||
Toggle::Toggle(Element *parent) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable), "button") {
|
||||
ToggleSizing(float width, float height, float floater_margin)
|
||||
: width(width), height(height),
|
||||
floater_width(height - floater_margin * 2.0f),
|
||||
floater_height(height - floater_margin * 2.0f),
|
||||
floater_margin(floater_margin) {}
|
||||
|
||||
float left_offset(bool checked) const {
|
||||
return checked ? width - floater_width - floater_margin : floater_margin;
|
||||
}
|
||||
};
|
||||
|
||||
static const ToggleSizing medium_toggle_sizing = ToggleSizing(64.0f, 36.0f, 6.0f);
|
||||
static const ToggleSizing large_toggle_sizing = ToggleSizing(144.0f, 72.0f, 8.0f);
|
||||
|
||||
static const ToggleSizing &get_toggle_sizing(ToggleSize size) {
|
||||
switch (size) {
|
||||
case ToggleSize::Medium:
|
||||
return medium_toggle_sizing;
|
||||
case ToggleSize::Large:
|
||||
return large_toggle_sizing;
|
||||
default:
|
||||
assert(false && "Invalid toggle size");
|
||||
}
|
||||
}
|
||||
|
||||
Toggle::Toggle(Element *parent, ToggleSize size) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable), "button") {
|
||||
this->size = size;
|
||||
const ToggleSizing &sizing = get_toggle_sizing(size);
|
||||
enable_focus();
|
||||
|
||||
set_width(toggle_width);
|
||||
set_height(toggle_height);
|
||||
set_border_radius(toggle_height * 0.5f);
|
||||
set_position(Position::Relative);
|
||||
|
||||
set_width(sizing.width);
|
||||
set_height(sizing.height);
|
||||
set_border_radius(sizing.height * 0.5f);
|
||||
set_opacity(0.9f);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_border_width(theme::border::width);
|
||||
// set_border_width(theme::border::width);
|
||||
set_border_color(theme::color::BW50);
|
||||
set_background_color(theme::color::Transparent);
|
||||
|
||||
checked_style.set_border_color(theme::color::Primary);
|
||||
inner_border_styles.checked.set_border_color(theme::color::Primary);
|
||||
|
||||
hover_style.set_border_color(theme::color::BW50);
|
||||
inner_border_styles.hover.set_border_color(theme::color::BW50);
|
||||
hover_style.set_background_color(theme::color::WhiteA5);
|
||||
|
||||
focus_style.set_border_color(theme::color::BW50);
|
||||
inner_border_styles.focus.set_border_color(theme::color::BW50);
|
||||
focus_style.set_background_color(theme::color::WhiteA5);
|
||||
|
||||
checked_hover_style.set_border_color(theme::color::Primary);
|
||||
inner_border_styles.checked_hover.set_border_color(theme::color::Primary);
|
||||
checked_hover_style.set_background_color(theme::color::PrimaryA30);
|
||||
|
||||
checked_focus_style.set_border_color(theme::color::Primary);
|
||||
inner_border_styles.checked_focus.set_border_color(theme::color::Primary);
|
||||
checked_focus_style.set_background_color(theme::color::PrimaryA30);
|
||||
|
||||
disabled_style.set_border_color(theme::color::BW50, 128);
|
||||
inner_border_styles.disabled.set_border_color(theme::color::BW50, 128);
|
||||
|
||||
checked_disabled_style.set_border_color(theme::color::PrimaryD, 128);
|
||||
inner_border_styles.checked_disabled.set_border_color(theme::color::PrimaryD, 128);
|
||||
|
||||
add_style(&checked_style, checked_state);
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
@@ -43,12 +91,27 @@ namespace recompui {
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
focus_border = context.create_element<FocusBorder>(this);
|
||||
focus_border->set_border_radius((sizing.height + theme::border::width * 4.0f + theme::border::width * 4.0f) * 0.5f);
|
||||
|
||||
inner_border = context.create_element<PseudoBorder>(this);
|
||||
inner_border->set_border_radius((sizing.height - theme::border::width * 2.0f) * 0.5f);
|
||||
inner_border->set_border_color(theme::color::BW50);
|
||||
inner_border->add_style(&inner_border_styles.checked, checked_state);
|
||||
inner_border->add_style(&inner_border_styles.hover, hover_state);
|
||||
inner_border->add_style(&inner_border_styles.focus, focus_state);
|
||||
inner_border->add_style(&inner_border_styles.checked_hover, { checked_state, hover_state });
|
||||
inner_border->add_style(&inner_border_styles.checked_focus, { checked_state, focus_state });
|
||||
inner_border->add_style(&inner_border_styles.disabled, disabled_state);
|
||||
inner_border->add_style(&inner_border_styles.checked_disabled, { checked_state, disabled_state });
|
||||
|
||||
floater = context.create_element<Element>(this);
|
||||
floater->set_position(Position::Relative);
|
||||
floater->set_top(2.0f);
|
||||
floater->set_width(toggle_floater_width);
|
||||
floater->set_height(toggle_floater_height);
|
||||
floater->set_border_radius(toggle_height * 0.5f);
|
||||
floater->set_position(Position::Absolute);
|
||||
floater->set_top(50.0f, Unit::Percent);
|
||||
floater->set_translate_2D(0, -50.0f, Unit::Percent);
|
||||
floater->set_width(sizing.floater_width);
|
||||
floater->set_height(sizing.floater_height);
|
||||
floater->set_border_radius(sizing.floater_height * 0.5f);
|
||||
floater->set_background_color(theme::color::TextDim);
|
||||
floater_checked_style.set_background_color(theme::color::Primary);
|
||||
floater_disabled_style.set_background_color(theme::color::TextDim, 128);
|
||||
@@ -60,6 +123,12 @@ namespace recompui {
|
||||
set_checked_internal(false, false, true, false);
|
||||
}
|
||||
|
||||
void Toggle::set_all_style_enabled(std::string_view style_name, bool enabled) {
|
||||
set_style_enabled(style_name, enabled);
|
||||
inner_border->set_style_enabled(style_name, enabled);
|
||||
floater->set_style_enabled(style_name, enabled);
|
||||
}
|
||||
|
||||
void Toggle::set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks) {
|
||||
if (this->checked != checked || setup) {
|
||||
this->checked = checked;
|
||||
@@ -80,13 +149,12 @@ namespace recompui {
|
||||
}
|
||||
}
|
||||
|
||||
set_style_enabled(checked_state, checked);
|
||||
floater->set_style_enabled(checked_state, checked);
|
||||
set_all_style_enabled(checked_state, checked);
|
||||
}
|
||||
}
|
||||
|
||||
float Toggle::floater_left_target() const {
|
||||
return checked ? 78.0f : 4.0f;
|
||||
return get_toggle_sizing(size).left_offset(checked);
|
||||
}
|
||||
|
||||
void Toggle::process_event(const Event &e) {
|
||||
@@ -99,19 +167,18 @@ namespace recompui {
|
||||
break;
|
||||
case EventType::Hover: {
|
||||
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||
set_style_enabled(hover_state, hover_active);
|
||||
floater->set_style_enabled(hover_state, hover_active);
|
||||
set_all_style_enabled(hover_state, hover_active);
|
||||
break;
|
||||
}
|
||||
case EventType::Focus: {
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
set_all_style_enabled(focus_state, focus_active);
|
||||
queue_update();
|
||||
break;
|
||||
}
|
||||
case EventType::Enable: {
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
floater->set_style_enabled(disabled_state, !enable_active);
|
||||
set_all_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
@@ -126,6 +193,7 @@ namespace recompui {
|
||||
std::chrono::high_resolution_clock::duration now = ultramodern::time_since_start();
|
||||
float delta_time = std::max(std::chrono::duration<float>(now - last_time).count(), 0.0f);
|
||||
last_time = now;
|
||||
bool should_queue_update = false;
|
||||
|
||||
constexpr float dp_speed = 740.0f;
|
||||
const float target = floater_left_target();
|
||||
@@ -140,11 +208,15 @@ namespace recompui {
|
||||
floater_left = target;
|
||||
}
|
||||
else {
|
||||
queue_update();
|
||||
should_queue_update = true;
|
||||
}
|
||||
|
||||
floater->set_left(floater_left, Unit::Dp);
|
||||
|
||||
if (should_queue_update) {
|
||||
queue_update();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -4,9 +4,17 @@
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum class ToggleSize {
|
||||
Medium,
|
||||
Large,
|
||||
Default = Large
|
||||
};
|
||||
|
||||
class Toggle : public Element {
|
||||
protected:
|
||||
Element *floater;
|
||||
Element *inner_border;
|
||||
Element *focus_border;
|
||||
float floater_left = 0.0f;
|
||||
std::chrono::high_resolution_clock::duration last_time;
|
||||
std::list<std::function<void(bool)>> checked_callbacks;
|
||||
@@ -17,22 +25,35 @@ namespace recompui {
|
||||
Style checked_focus_style;
|
||||
Style disabled_style;
|
||||
Style checked_disabled_style;
|
||||
|
||||
struct {
|
||||
Style checked;
|
||||
Style hover;
|
||||
Style focus;
|
||||
Style checked_hover;
|
||||
Style checked_focus;
|
||||
Style disabled;
|
||||
Style checked_disabled;
|
||||
} inner_border_styles;
|
||||
|
||||
Style floater_checked_style;
|
||||
Style floater_disabled_style;
|
||||
Style floater_disabled_checked_style;
|
||||
bool checked = false;
|
||||
ToggleSize size = ToggleSize::Default;
|
||||
|
||||
void set_checked_internal(bool checked, bool animate, bool setup, bool trigger_callbacks);
|
||||
void set_all_style_enabled(std::string_view style_name, bool enabled);
|
||||
float floater_left_target() const;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Toggle"; }
|
||||
public:
|
||||
Toggle(Element *parent);
|
||||
Toggle(Element *parent, ToggleSize size = ToggleSize::Default);
|
||||
void set_checked(bool checked);
|
||||
bool is_checked() const;
|
||||
void add_checked_callback(std::function<void(bool)> callback);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
} // namespace recompui
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace recompui {
|
||||
Update,
|
||||
Navigate,
|
||||
MouseButton,
|
||||
MenuAction,
|
||||
Count
|
||||
};
|
||||
|
||||
@@ -65,6 +66,16 @@ namespace recompui {
|
||||
Auto
|
||||
};
|
||||
|
||||
enum class MenuAction {
|
||||
None,
|
||||
Accept,
|
||||
Apply,
|
||||
Back,
|
||||
Toggle,
|
||||
TabLeft,
|
||||
TabRight,
|
||||
};
|
||||
|
||||
template <typename Enum, typename = std::enable_if_t<std::is_enum_v<Enum>>>
|
||||
constexpr uint32_t Events(Enum first) {
|
||||
return 1u << static_cast<uint32_t>(first);
|
||||
@@ -113,7 +124,11 @@ namespace recompui {
|
||||
bool pressed;
|
||||
};
|
||||
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, EventMouseButton, std::monostate>;
|
||||
struct EventMenuAction {
|
||||
MenuAction action;
|
||||
};
|
||||
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, EventMouseButton, EventMenuAction, std::monostate>;
|
||||
|
||||
struct Event {
|
||||
EventType type;
|
||||
@@ -182,6 +197,13 @@ namespace recompui {
|
||||
e.variant = EventMouseButton{ x, y, button, pressed };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event menu_action_event(MenuAction action) {
|
||||
Event e;
|
||||
e.type = EventType::MenuAction;
|
||||
e.variant = EventMenuAction{ action };
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
enum class Display {
|
||||
|
||||
@@ -87,6 +87,12 @@ bool convert_event(const recompui::Event& in, RecompuiEventData& out) {
|
||||
case recompui::EventType::Update:
|
||||
// No data for an update event.
|
||||
break;
|
||||
case recompui::EventType::MenuAction:
|
||||
{
|
||||
const recompui::EventMenuAction &menu_action = std::get<recompui::EventMenuAction>(in.variant);
|
||||
out.data.menu_action.action = static_cast<RecompuiMenuAction>(menu_action.action);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return !skip;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "ui_color_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ElementColor::ElementColor(const Rml::String& tag) : Rml::Element(tag) {
|
||||
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Inline);
|
||||
}
|
||||
|
||||
void ElementColor::check_color_attribute(const std::string &attr, const std::string &check, theme::color color) {
|
||||
if (attr == check) {
|
||||
recompui::Element this_compat(this);
|
||||
this_compat.set_color(color);
|
||||
}
|
||||
}
|
||||
|
||||
void ElementColor::OnAttributeChange(const Rml::ElementAttributes& changed_attributes) {
|
||||
Rml::Element::OnAttributeChange(changed_attributes);
|
||||
|
||||
for (auto &attr : changed_attributes) {
|
||||
std::string name = attr.first;
|
||||
check_color_attribute(name, "primary", theme::color::Primary);
|
||||
check_color_attribute(name, "secondary", theme::color::Secondary);
|
||||
check_color_attribute(name, "warning", theme::color::Warning);
|
||||
check_color_attribute(name, "danger", theme::color::Danger);
|
||||
check_color_attribute(name, "success", theme::color::Success);
|
||||
}
|
||||
}
|
||||
|
||||
ElementColor::~ElementColor() {
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_theme.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ElementColor : public Rml::Element {
|
||||
private:
|
||||
recompui::Element *element;
|
||||
public:
|
||||
ElementColor(const Rml::String& tag);
|
||||
void OnAttributeChange(const Rml::ElementAttributes& changed_attributes) override;
|
||||
virtual ~ElementColor();
|
||||
void check_color_attribute(const std::string &attr, const std::string &check, theme::color color);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -18,19 +18,19 @@ Rml::DataModelHandle general_model_handle;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
|
||||
int recompui::config_tab_to_index(recompui::ConfigTab tab) {
|
||||
int recompui::config_tab_to_index(recompui::ConfigTabId tab) {
|
||||
switch (tab) {
|
||||
case recompui::ConfigTab::General:
|
||||
case recompui::ConfigTabId::General:
|
||||
return 0;
|
||||
case recompui::ConfigTab::Controls:
|
||||
case recompui::ConfigTabId::Controls:
|
||||
return 1;
|
||||
case recompui::ConfigTab::Graphics:
|
||||
case recompui::ConfigTabId::Graphics:
|
||||
return 2;
|
||||
case recompui::ConfigTab::Sound:
|
||||
case recompui::ConfigTabId::Sound:
|
||||
return 3;
|
||||
case recompui::ConfigTab::Mods:
|
||||
case recompui::ConfigTabId::Mods:
|
||||
return 4;
|
||||
case recompui::ConfigTab::Debug:
|
||||
case recompui::ConfigTabId::Debug:
|
||||
return 5;
|
||||
default:
|
||||
assert(false && "Unknown config tab.");
|
||||
@@ -380,7 +380,7 @@ class ConfigTabsetListener : public Rml::EventListener {
|
||||
void ProcessEvent(Rml::Event& event) override {
|
||||
if (event.GetId() == Rml::EventId::Tabchange) {
|
||||
int tab_index = event.GetParameter<int>("tab_index", 0);
|
||||
bool in_mod_tab = (tab_index == recompui::config_tab_to_index(recompui::ConfigTab::Mods));
|
||||
bool in_mod_tab = (tab_index == recompui::config_tab_to_index(recompui::ConfigTabId::Mods));
|
||||
if (in_mod_tab) {
|
||||
recompui::set_config_tabset_mod_nav();
|
||||
}
|
||||
@@ -703,7 +703,7 @@ void recompui::toggle_fullscreen() {
|
||||
graphics_model_handle.DirtyVariable("wm_option");
|
||||
}
|
||||
|
||||
void recompui::set_config_tab(ConfigTab tab) {
|
||||
void recompui::set_config_tab(ConfigTabId tab) {
|
||||
get_config_tabset()->SetActiveTab(config_tab_to_index(tab));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
#include "ui_config_modal.h"
|
||||
#include "config/ui_config_tab_graphics.h"
|
||||
#include "config/ui_config_tab_controls.h"
|
||||
#include "banjo_config.h"
|
||||
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Modal *config_modal = nullptr;
|
||||
|
||||
|
||||
|
||||
static recomp::config::Config example_manifest("Example Config", "example_config", true);
|
||||
static ConfigTab example_tab(&example_manifest);
|
||||
|
||||
static recomp::config::Config example_manifest2("No apply", "example_config_no_apply", false);
|
||||
static ConfigTab example_tab2(&example_manifest2);
|
||||
|
||||
// ConfigTabGeneral config_tab_general = ConfigTabGeneral();
|
||||
|
||||
static std::vector<TabContext *> config_tabs = {
|
||||
// &config_tab_general,
|
||||
&config_tab_graphics,
|
||||
&config_tab_controls,
|
||||
// other hardcoded tabs
|
||||
};
|
||||
|
||||
enum class TheThings {
|
||||
This,
|
||||
That,
|
||||
TheOther
|
||||
};
|
||||
|
||||
const std::vector<recomp::config::ConfigOptionEnumOption> example_enum_options = {
|
||||
{TheThings::TheOther, "theotherr", "The Other ñ"},
|
||||
{TheThings::That, "that", "That"},
|
||||
{TheThings::This, "this", "This"},
|
||||
};
|
||||
|
||||
enum class TheUnsureThings {
|
||||
Yes,
|
||||
Maybe,
|
||||
Unsure,
|
||||
No,
|
||||
Nah,
|
||||
|
||||
size
|
||||
};
|
||||
|
||||
const std::vector<recomp::config::ConfigOptionEnumOption> example_enum2_options = {
|
||||
{TheUnsureThings::Yes, "Yes?"},
|
||||
{TheUnsureThings::Maybe, "Maybe"},
|
||||
{TheUnsureThings::Unsure, "Unsure"},
|
||||
{TheUnsureThings::No, "No"},
|
||||
{TheUnsureThings::Nah, "Nah"}
|
||||
};
|
||||
|
||||
// typedef std::variant<std::monostate, uint32_t, double, std::string> ConfigValueVariant;
|
||||
void print_option_value_change_s(const std::string &option_id, std::string value, std::string prev_value) {
|
||||
printf("Option '%s' changed from '%s' to '%s'\n",
|
||||
option_id.c_str(),
|
||||
prev_value.c_str(),
|
||||
value.c_str()
|
||||
);
|
||||
}
|
||||
void print_option_value_change_i(const std::string &option_id, int value, int prev_value) {
|
||||
printf("Option '%s' changed from '%d' to '%d'\n",
|
||||
option_id.c_str(),
|
||||
prev_value,
|
||||
value
|
||||
);
|
||||
}
|
||||
void print_option_value_change_f(const std::string &option_id, double value, double prev_value) {
|
||||
printf("Option '%s' changed from '%f' to '%f'\n",
|
||||
option_id.c_str(),
|
||||
prev_value,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
void init_example_config() {
|
||||
using OptionChangeContext = recomp::config::OptionChangeContext;
|
||||
example_manifest.add_number_option(
|
||||
"example_option_number",
|
||||
"Rumble strength",
|
||||
"This is just a little example hehe",
|
||||
0.0,
|
||||
10.0,
|
||||
1.0,
|
||||
0,
|
||||
false,
|
||||
5
|
||||
);
|
||||
example_manifest.add_option_change_callback("example_option_number", [](recomp::config::ConfigValueVariant value, recomp::config::ConfigValueVariant prev_value, OptionChangeContext change_context) {
|
||||
print_option_value_change_f("example_option_number", std::get<double>(value), std::get<double>(prev_value));
|
||||
});
|
||||
example_manifest.add_enum_option(
|
||||
"example_option_enum",
|
||||
"Example Option Enum",
|
||||
"This is just another little example haha!",
|
||||
example_enum_options,
|
||||
TheThings::TheOther
|
||||
);
|
||||
example_manifest.add_bool_option(
|
||||
"example_option_bool",
|
||||
"Option Bool",
|
||||
"Just a stupid fucking bool",
|
||||
true
|
||||
);
|
||||
example_manifest.add_bool_option(
|
||||
"example_option_bool2",
|
||||
"Option Bool 2",
|
||||
"Just another stupid fucking bool",
|
||||
false
|
||||
);
|
||||
|
||||
// example_manifest.add_option_change_callback("example_option_enum", [](recomp::config::ConfigValueVariant value, recomp::config::ConfigValueVariant prev_value) {
|
||||
// print_option_value_change_i("example_option_enum", std::get<uint32_t>(value), std::get<uint32_t>(prev_value));
|
||||
// TheThings new_value = static_cast<TheThings>(std::get<uint32_t>(value));
|
||||
// if (new_value == TheThings::This) {
|
||||
// example_manifest.set_option_value("example_option_enum2", static_cast<uint32_t>(TheUnsureThings::Yes));
|
||||
// example_manifest.update_option_enum_details("example_option_enum2", "");
|
||||
// example_manifest.update_option_disabled("example_option_enum2", false);
|
||||
// example_manifest.update_option_hidden("example_option_string", false);
|
||||
// }
|
||||
// if (new_value == TheThings::That) {
|
||||
// example_manifest.update_option_enum_details("example_option_enum2", "surprise bitch");
|
||||
// example_manifest.update_option_disabled("example_option_enum2", false);
|
||||
// example_manifest.update_option_hidden("example_option_string", true);
|
||||
// }
|
||||
// if (new_value == TheThings::TheOther) {
|
||||
// example_manifest.update_option_enum_details("example_option_enum2", "im disabled uwu");
|
||||
// example_manifest.update_option_disabled("example_option_enum2", true);
|
||||
// example_manifest.update_option_description("example_option_enum", "we did this on the flyyyyy");
|
||||
// example_manifest.update_option_hidden("example_option_string", false);
|
||||
// }
|
||||
// });
|
||||
example_manifest.add_string_option(
|
||||
"example_option_string",
|
||||
"Example Option String",
|
||||
"string example...! description........!!!!",
|
||||
"defaultma"
|
||||
);
|
||||
example_manifest.add_option_change_callback("example_option_string", [](recomp::config::ConfigValueVariant value, recomp::config::ConfigValueVariant prev_value, OptionChangeContext change_context) {
|
||||
print_option_value_change_s("example_option_string", std::get<std::string>(value), std::get<std::string>(prev_value));
|
||||
});
|
||||
example_manifest.add_enum_option(
|
||||
"example_option_enum2",
|
||||
"Choosy",
|
||||
"choosyyyyyy",
|
||||
example_enum2_options,
|
||||
TheUnsureThings::Unsure
|
||||
);
|
||||
example_manifest.add_option_change_callback("example_option_enum2", [](recomp::config::ConfigValueVariant value, recomp::config::ConfigValueVariant prev_value, OptionChangeContext change_context) {
|
||||
print_option_value_change_i("example_option_enum2", std::get<uint32_t>(value), std::get<uint32_t>(prev_value));
|
||||
});
|
||||
example_manifest.set_apply_callback([]() {
|
||||
printf("Example config applied!\n");
|
||||
});
|
||||
|
||||
for (int i = 2; i < 10; ++i) {
|
||||
example_manifest.add_string_option(
|
||||
"example_option_string" + std::to_string(i),
|
||||
"Example Option String " + std::to_string(i),
|
||||
"string example...! description........!!!!",
|
||||
"defaultma"
|
||||
);
|
||||
}
|
||||
|
||||
example_manifest.add_option_disable_dependency(
|
||||
"example_option_enum",
|
||||
"example_option_enum2",
|
||||
TheUnsureThings::Unsure
|
||||
);
|
||||
|
||||
example_manifest.add_option_hidden_dependency(
|
||||
"example_option_string2",
|
||||
"example_option_enum2",
|
||||
TheUnsureThings::Unsure
|
||||
);
|
||||
example_manifest.add_option_hidden_dependency(
|
||||
"example_option_string3",
|
||||
"example_option_enum2",
|
||||
TheUnsureThings::Unsure
|
||||
);
|
||||
example_manifest.add_option_hidden_dependency(
|
||||
"example_option_string4",
|
||||
"example_option_enum2",
|
||||
TheUnsureThings::Unsure
|
||||
);
|
||||
|
||||
example_manifest.load_config();
|
||||
|
||||
example_manifest2.add_number_option(
|
||||
"example_option_number_no_apply",
|
||||
"Example Option Number No Apply",
|
||||
"words words words words PUNCHLINE",
|
||||
0.0,
|
||||
10.0,
|
||||
1.0,
|
||||
0,
|
||||
false,
|
||||
5
|
||||
);
|
||||
example_manifest2.add_option_change_callback("example_option_number_no_apply", [](recomp::config::ConfigValueVariant value, recomp::config::ConfigValueVariant prev_value, OptionChangeContext change_context) {
|
||||
print_option_value_change_f("example_option_number_no_apply", std::get<double>(value), std::get<double>(prev_value));
|
||||
});
|
||||
|
||||
example_manifest2.load_config();
|
||||
|
||||
add_config_modal_tab(&example_tab);
|
||||
add_config_modal_tab(&example_tab2);
|
||||
}
|
||||
|
||||
void add_config_modal_tab(TabContext *tab, bool add_to_front) {
|
||||
if (add_to_front) {
|
||||
config_tabs.insert(config_tabs.begin(), tab);
|
||||
} else {
|
||||
config_tabs.push_back(tab);
|
||||
}
|
||||
}
|
||||
|
||||
void init_config_modal() {
|
||||
init_example_config();
|
||||
|
||||
config_modal = Modal::create_modal(ModalType::Fullscreen);
|
||||
config_modal->modal_root_context.open();
|
||||
for (auto tab : config_tabs) {
|
||||
config_modal->add_tab(tab);
|
||||
}
|
||||
config_modal->set_selected_tab(0);
|
||||
config_modal->set_on_close_callback([]() {
|
||||
banjo::save_config();
|
||||
});
|
||||
config_modal->modal_root_context.close();
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_modal.h"
|
||||
#include "config/ui_config_tab_manifest.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
extern Modal *config_modal;
|
||||
|
||||
void add_config_modal_tab(TabContext *tab, bool add_to_front = false);
|
||||
void init_config_modal();
|
||||
|
||||
} // namespace recompui
|
||||
@@ -7,8 +7,6 @@ namespace recompinput {
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ConfigPageControls *controls_page = nullptr;
|
||||
|
||||
static bool is_multiplayer_enabled() {
|
||||
return true;
|
||||
}
|
||||
@@ -28,11 +26,11 @@ ElementConfigPageControls::ElementConfigPageControls(const Rml::String& tag) : R
|
||||
recompui::Element this_compat(this);
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
controls_page = context.create_element<ConfigPageControls>(
|
||||
&this_compat,
|
||||
recompinput::get_num_players(),
|
||||
temp_game_input_contexts
|
||||
);
|
||||
// controls_page = context.create_element<ConfigPageControls>(
|
||||
// &this_compat,
|
||||
// recompinput::get_num_players(),
|
||||
// temp_game_input_contexts
|
||||
// );
|
||||
}
|
||||
|
||||
ElementConfigPageControls::~ElementConfigPageControls() {
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_config_page.h"
|
||||
#include "ui_config_page_controls.h"
|
||||
#include "config/ui_config_page_controls.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
extern ConfigPageControls *controls_page;
|
||||
|
||||
class ElementConfigPageControls : public Rml::Element {
|
||||
public:
|
||||
ElementConfigPageControls(const Rml::String& tag);
|
||||
|
||||
@@ -12,6 +12,7 @@ static RecompCustomElement custom_elements[] = {
|
||||
CUSTOM_ELEMENT("recomp-config-sub-menu", recompui::ElementConfigSubMenu),
|
||||
CUSTOM_ELEMENT("recomp-config-page-example", recompui::ElementConfigPageExample),
|
||||
CUSTOM_ELEMENT("recomp-config-page-controls", recompui::ElementConfigPageControls),
|
||||
CUSTOM_ELEMENT("recomp-color", recompui::ElementColor),
|
||||
};
|
||||
|
||||
void recompui::register_custom_elements() {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "ui_config_sub_menu.h"
|
||||
#include "ui_config_page_example.h"
|
||||
#include "ui_config_page_controls_element.h"
|
||||
#include "ui_color_element.h"
|
||||
|
||||
namespace recompui {
|
||||
void register_custom_elements();
|
||||
|
||||
@@ -85,21 +85,21 @@ public:
|
||||
);
|
||||
recompui::register_event(listener, "open_controls",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recompui::set_config_tab(recompui::ConfigTab::Controls);
|
||||
recompui::set_config_tab(recompui::ConfigTabId::Controls);
|
||||
recompui::hide_all_contexts();
|
||||
recompui::show_context(recompui::get_config_context_id(), "");
|
||||
}
|
||||
);
|
||||
recompui::register_event(listener, "open_settings",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recompui::set_config_tab(recompui::ConfigTab::General);
|
||||
recompui::set_config_tab(recompui::ConfigTabId::General);
|
||||
recompui::hide_all_contexts();
|
||||
recompui::show_context(recompui::get_config_context_id(), "");
|
||||
}
|
||||
);
|
||||
recompui::register_event(listener, "open_mods",
|
||||
[](const std::string ¶m, Rml::Event &event) {
|
||||
recompui::set_config_tab(recompui::ConfigTab::Mods);
|
||||
recompui::set_config_tab(recompui::ConfigTabId::Mods);
|
||||
recompui::hide_all_contexts();
|
||||
recompui::show_context(recompui::get_config_context_id(), "");
|
||||
}
|
||||
|
||||
@@ -340,4 +340,4 @@ namespace recompui {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
+22
-14
@@ -305,7 +305,7 @@ void ModMenu::mod_selected(uint32_t mod_index) {
|
||||
|
||||
if (active_mod_index >= 0) {
|
||||
std::string thumbnail_src = generate_thumbnail_src_for_mod(mod_details[mod_index].mod_id);
|
||||
const recomp::mods::ConfigSchema &config_schema = recomp::mods::get_mod_config_schema(mod_details[active_mod_index].mod_id);
|
||||
const recomp::config::ConfigSchema &config_schema = recomp::mods::get_mod_config_schema(mod_details[active_mod_index].mod_id);
|
||||
bool toggle_checked = is_mod_enabled_or_auto(mod_details[mod_index].mod_id);
|
||||
bool auto_enabled = recomp::mods::is_mod_auto_enabled(mod_details[mod_index].mod_id);
|
||||
bool toggle_enabled = !auto_enabled && (mod_details[mod_index].runtime_toggleable || !ultramodern::is_game_started());
|
||||
@@ -444,11 +444,15 @@ ContextId get_config_sub_menu_context_id() {
|
||||
return sub_menu_context;
|
||||
}
|
||||
|
||||
bool ModMenu::handle_special_config_options(const recomp::mods::ConfigOption& option, const recomp::mods::ConfigValueVariant& config_value) {
|
||||
bool ModMenu::handle_special_config_options(const recomp::config::ConfigOption& option, const recomp::config::ConfigValueVariant& config_value) {
|
||||
if (banjo::renderer::is_texture_pack_enable_config_option(option, true)) {
|
||||
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
|
||||
const recomp::config::ConfigOptionEnum &option_enum = std::get<recomp::config::ConfigOptionEnum>(option.variant);
|
||||
std::vector<std::string> option_names;
|
||||
for (const auto &opt : option_enum.options) {
|
||||
option_names.push_back(opt.name);
|
||||
}
|
||||
|
||||
config_sub_menu->add_radio_option(option.id, option.name, option.description, std::get<uint32_t>(config_value), option_enum.options,
|
||||
config_sub_menu->add_radio_option(option.id, option.name, option.description, std::get<uint32_t>(config_value), option_names,
|
||||
[this](const std::string &id, uint32_t value) {
|
||||
mod_enum_option_changed(id, value);
|
||||
mod_hd_textures_enabled_changed(value);
|
||||
@@ -470,9 +474,9 @@ void ModMenu::mod_configure_requested() {
|
||||
sub_menu_context.open();
|
||||
config_sub_menu->clear_options();
|
||||
|
||||
const recomp::mods::ConfigSchema &config_schema = recomp::mods::get_mod_config_schema(mod_details[active_mod_index].mod_id);
|
||||
for (const recomp::mods::ConfigOption &option : config_schema.options) {
|
||||
recomp::mods::ConfigValueVariant config_value = recomp::mods::get_mod_config_value(mod_details[active_mod_index].mod_id, option.id);
|
||||
const recomp::config::ConfigSchema &config_schema = recomp::mods::get_mod_config_schema(mod_details[active_mod_index].mod_id);
|
||||
for (const recomp::config::ConfigOption &option : config_schema.options) {
|
||||
recomp::config::ConfigValueVariant config_value = recomp::mods::get_mod_config_value(mod_details[active_mod_index].mod_id, option.id);
|
||||
if (std::holds_alternative<std::monostate>(config_value)) {
|
||||
continue;
|
||||
}
|
||||
@@ -482,19 +486,23 @@ void ModMenu::mod_configure_requested() {
|
||||
}
|
||||
|
||||
switch (option.type) {
|
||||
case recomp::mods::ConfigOptionType::Enum: {
|
||||
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
|
||||
config_sub_menu->add_radio_option(option.id, option.name, option.description, std::get<uint32_t>(config_value), option_enum.options,
|
||||
case recomp::config::ConfigOptionType::Enum: {
|
||||
const recomp::config::ConfigOptionEnum &option_enum = std::get<recomp::config::ConfigOptionEnum>(option.variant);
|
||||
std::vector<std::string> option_names;
|
||||
for (const auto &opt : option_enum.options) {
|
||||
option_names.push_back(opt.name);
|
||||
}
|
||||
config_sub_menu->add_radio_option(option.id, option.name, option.description, std::get<uint32_t>(config_value), option_names,
|
||||
[this](const std::string &id, uint32_t value){ mod_enum_option_changed(id, value); });
|
||||
break;
|
||||
}
|
||||
case recomp::mods::ConfigOptionType::Number: {
|
||||
const recomp::mods::ConfigOptionNumber &option_number = std::get<recomp::mods::ConfigOptionNumber>(option.variant);
|
||||
case recomp::config::ConfigOptionType::Number: {
|
||||
const recomp::config::ConfigOptionNumber &option_number = std::get<recomp::config::ConfigOptionNumber>(option.variant);
|
||||
config_sub_menu->add_slider_option(option.id, option.name, option.description, std::get<double>(config_value), option_number.min, option_number.max, option_number.step, option_number.percent,
|
||||
[this](const std::string &id, double value){ mod_number_option_changed(id, value); });
|
||||
break;
|
||||
}
|
||||
case recomp::mods::ConfigOptionType::String: {
|
||||
case recomp::config::ConfigOptionType::String: {
|
||||
config_sub_menu->add_text_option(option.id, option.name, option.description, std::get<std::string>(config_value),
|
||||
[this](const std::string &id, const std::string &value){ mod_string_option_changed(id, value); });
|
||||
break;
|
||||
@@ -587,7 +595,7 @@ void ModMenu::create_mod_list() {
|
||||
}
|
||||
|
||||
Rml::ElementTabSet* tabset = recompui::get_config_tabset();
|
||||
if (tabset && tabset->GetActiveTab() == recompui::config_tab_to_index(ConfigTab::Mods)) {
|
||||
if (tabset && tabset->GetActiveTab() == recompui::config_tab_to_index(ConfigTabId::Mods)) {
|
||||
recompui::set_config_tabset_mod_nav();
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ private:
|
||||
void mod_selected(uint32_t mod_index);
|
||||
void mod_dragged(uint32_t mod_index, EventDrag drag);
|
||||
void mod_configure_requested();
|
||||
bool handle_special_config_options(const recomp::mods::ConfigOption& option, const recomp::mods::ConfigValueVariant& config_value);
|
||||
bool handle_special_config_options(const recomp::config::ConfigOption& option, const recomp::config::ConfigValueVariant& config_value);
|
||||
void mod_enum_option_changed(const std::string &id, uint32_t value);
|
||||
void mod_string_option_changed(const std::string &id, const std::string &value);
|
||||
void mod_number_option_changed(const std::string &id, double value);
|
||||
|
||||
+67
-27
@@ -27,6 +27,7 @@
|
||||
#include "ui_mod_installer.h"
|
||||
#include "ui_renderer.h"
|
||||
#include "ui_assign_players_modal.h"
|
||||
#include "ui_config_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;
|
||||
@@ -239,6 +240,7 @@ public:
|
||||
config_menu_controller->load_document();
|
||||
recompui::init_prompt_context();
|
||||
recompui::init_assign_players_modal();
|
||||
recompui::init_config_modal();
|
||||
}
|
||||
|
||||
void unload() {
|
||||
@@ -428,6 +430,12 @@ public:
|
||||
context_details.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
void report_removed_element(Rml::Element* element) {
|
||||
if (prev_focused == element) {
|
||||
prev_focused = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<UIState> ui_state;
|
||||
@@ -440,6 +448,11 @@ void recompui::get_window_size(int& width, int& height) {
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
}
|
||||
|
||||
void recompui::report_removed_element(Rml::Element* element) {
|
||||
std::lock_guard lock{ ui_state_mutex };
|
||||
ui_state->report_removed_element(element);
|
||||
}
|
||||
|
||||
inline const std::string read_file_to_string(std::filesystem::path path) {
|
||||
std::ifstream stream = std::ifstream{path};
|
||||
std::ostringstream ss;
|
||||
@@ -465,33 +478,52 @@ bool recompui::try_deque_event(SDL_Event& out) {
|
||||
return ui_event_queue.try_dequeue(out);
|
||||
}
|
||||
|
||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||
// TODO: Needs the profile index.
|
||||
// Configurable accept button in menu
|
||||
auto menuAcceptBinding0 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0);
|
||||
auto menuAcceptBinding1 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
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;
|
||||
recompui::MenuAction recompui::menu_action_mapping::menu_action_from_rml_key(const Rml::Input::KeyIdentifier& key) {
|
||||
auto it = recompui::menu_action_mapping::rml_key_to_action.find(key);
|
||||
if (it != recompui::menu_action_mapping::rml_key_to_action.end()) {
|
||||
return it->second.action;
|
||||
}
|
||||
return recompui::MenuAction::None;
|
||||
}
|
||||
|
||||
// Configurable apply button in menu
|
||||
auto menuApplyBinding0 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0);
|
||||
auto menuApplyBinding1 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
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;
|
||||
}
|
||||
// Extends RmlSDL::ConvertKey for some extra mapping slots
|
||||
Rml::Input::KeyIdentifier convert_sdl_to_rml(int sdl_key) {
|
||||
Rml::Input::KeyIdentifier identifier = RmlSDL::ConvertKey(sdl_key);
|
||||
if (identifier == Rml::Input::KeyIdentifier::KI_UNKNOWN) {
|
||||
switch (sdl_key) {
|
||||
case SDLK_F15: return Rml::Input::KI_F15;
|
||||
case SDLK_F16: return Rml::Input::KI_F16;
|
||||
case SDLK_F17: return Rml::Input::KI_F17;
|
||||
case SDLK_F18: return Rml::Input::KI_F18;
|
||||
case SDLK_F19: return Rml::Input::KI_F19;
|
||||
case SDLK_F20: return Rml::Input::KI_F20;
|
||||
case SDLK_F21: return Rml::Input::KI_F21;
|
||||
case SDLK_F22: return Rml::Input::KI_F22;
|
||||
case SDLK_F23: return Rml::Input::KI_F23;
|
||||
case SDLK_F24: return Rml::Input::KI_F24;
|
||||
}
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
// Allows closing the menu
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1);
|
||||
// note - magic number: 0 is InputType::None
|
||||
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;
|
||||
bool check_menu_button_pressed(int profile_index, recomp::GameInput input, int32_t event_button) {
|
||||
auto menuBinding0 = recomp::get_input_binding(profile_index, input, 0);
|
||||
auto menuBinding1 = recomp::get_input_binding(profile_index, input, 1);
|
||||
if ((menuBinding0.input_type != recomp::InputType::None && event_button == menuBinding0.input_id) ||
|
||||
(menuBinding1.input_type != recomp::InputType::None && event_button == menuBinding1.input_id)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||
int player1_profile = recomp::get_input_profile_for_player(0, recomp::InputDevice::Controller);
|
||||
|
||||
for (const auto& mapping_pair : recompui::menu_action_mapping::rml_key_to_action) {
|
||||
const auto& mapping = mapping_pair.second;
|
||||
if (check_menu_button_pressed(player1_profile, mapping.input, button.button)) {
|
||||
return mapping.sdl;
|
||||
}
|
||||
}
|
||||
|
||||
switch (button.button) {
|
||||
@@ -635,7 +667,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: {
|
||||
int sdl_key = cont_button_to_key(cur_event.cbutton);
|
||||
if (context_capturing_input && sdl_key) {
|
||||
ui_state->context->ProcessKeyDown(RmlSDL::ConvertKey(sdl_key), 0);
|
||||
ui_state->context->ProcessKeyDown(convert_sdl_to_rml(sdl_key), 0);
|
||||
latest_controller_key_pressed = sdl_key;
|
||||
next_repeat_time = clock::now() + start_repeat_delay;
|
||||
}
|
||||
@@ -651,6 +683,14 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
Rml::Debugger::SetVisible(!Rml::Debugger::IsVisible());
|
||||
}
|
||||
}
|
||||
if (cur_event.key.keysym.scancode == SDL_Scancode::SDL_SCANCODE_F6) {
|
||||
// recompui::show_context(recompui::config_modal->modal_root_context, "");
|
||||
if (recompui::config_modal->is_open_now()) {
|
||||
recompui::config_modal->close();
|
||||
} else {
|
||||
recompui::config_modal->open();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EventType::SDL_USEREVENT:
|
||||
if (cur_event.user.code == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY) {
|
||||
@@ -675,7 +715,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
non_mouse_interacted = true;
|
||||
int sdl_key = cont_axis_to_key(cur_event.caxis, axis_value);
|
||||
if (context_capturing_input && sdl_key) {
|
||||
ui_state->context->ProcessKeyDown(RmlSDL::ConvertKey(sdl_key), 0);
|
||||
ui_state->context->ProcessKeyDown(convert_sdl_to_rml(sdl_key), 0);
|
||||
latest_controller_key_pressed = sdl_key;
|
||||
next_repeat_time = clock::now() + start_repeat_delay;
|
||||
}
|
||||
@@ -739,7 +779,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
if (latest_controller_key_pressed != SDLK_UNKNOWN) {
|
||||
clock::time_point now = clock::now();
|
||||
if (now >= next_repeat_time) {
|
||||
ui_state->context->ProcessKeyDown(RmlSDL::ConvertKey(latest_controller_key_pressed), 0);
|
||||
ui_state->context->ProcessKeyDown(convert_sdl_to_rml(latest_controller_key_pressed), 0);
|
||||
next_repeat_time += repeat_rate;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user