mirror of
https://github.com/BanjoRecomp/BanjoRecomp
synced 2026-06-16 13:59:44 -04:00
switch ui/input to be using recompfrontend library (unfinished)
This commit is contained in:
+6
-62
@@ -62,6 +62,7 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi)
|
||||
target_compile_definitions(rmlui_core PRIVATE LUNASVG_BUILD_STATIC)
|
||||
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RecompFrontend)
|
||||
|
||||
target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src)
|
||||
|
||||
@@ -149,14 +150,10 @@ add_executable(BanjoRecompiled)
|
||||
|
||||
set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/main/main.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/support.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/rt64_render_context.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/theme.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/game/input.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/controls.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/config.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/debug.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp
|
||||
@@ -164,69 +161,11 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/game/recomp_data_api.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_assign_players_modal.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_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_element.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_player_card.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_details_panel.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_installer.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp
|
||||
${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
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_icon_button.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_config_page.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_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
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp
|
||||
${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
|
||||
|
||||
${CMAKE_SOURCE_DIR}/rsp/n_aspMain.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND SOURCES ${CMAKE_SOURCE_DIR}/src/main/support_apple.mm)
|
||||
endif()
|
||||
|
||||
target_include_directories(BanjoRecompiled PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/N64Recomp/include
|
||||
@@ -240,6 +179,9 @@ target_include_directories(BanjoRecompiled PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/lib/rt64/src
|
||||
${CMAKE_SOURCE_DIR}/lib/rt64/src/rhi
|
||||
${CMAKE_SOURCE_DIR}/lib/rt64/src/render
|
||||
${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompinput/include
|
||||
${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompui/include
|
||||
${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompui/src
|
||||
${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include
|
||||
${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/include
|
||||
${CMAKE_SOURCE_DIR}/lib/slot_map/slot_map
|
||||
@@ -377,6 +319,8 @@ target_link_libraries(BanjoRecompiled PRIVATE
|
||||
rt64
|
||||
RmlUi::Core
|
||||
RmlUi::Debugger
|
||||
recompinput
|
||||
recompui
|
||||
nfd
|
||||
lunasvg
|
||||
)
|
||||
|
||||
+18
-42
@@ -2,64 +2,40 @@
|
||||
#define __BANJO_CONFIG_H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "recomp_input.h"
|
||||
|
||||
namespace banjo {
|
||||
constexpr std::u8string_view program_id = u8"BanjoRecompiled";
|
||||
constexpr std::string_view program_name = "Banjo: Recompiled";
|
||||
inline const std::u8string program_id = u8"BanjoRecompiled";
|
||||
inline const std::string program_name = "Banjo: Recompiled";
|
||||
|
||||
namespace configkeys {
|
||||
namespace general {
|
||||
inline const std::string camera_invert_mode = "camera_invert_mode";
|
||||
inline const std::string analog_cam_mode = "analog_cam_mode";
|
||||
inline const std::string analog_camera_invert_mode = "analog_camera_invert_mode";
|
||||
}
|
||||
|
||||
namespace sound {
|
||||
inline const std::string bgm_volume = "bgm_volume";
|
||||
}
|
||||
}
|
||||
|
||||
// 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 init_config();
|
||||
|
||||
void reset_input_bindings();
|
||||
void reset_cont_input_bindings(int profile_index);
|
||||
void reset_kb_input_bindings(int profile_index);
|
||||
void reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input);
|
||||
|
||||
std::filesystem::path get_app_folder_path();
|
||||
|
||||
bool get_debug_mode_enabled();
|
||||
void set_debug_mode_enabled(bool enabled);
|
||||
|
||||
enum class CameraInvertMode {
|
||||
InvertNone,
|
||||
InvertX,
|
||||
InvertY,
|
||||
InvertBoth,
|
||||
OptionCount
|
||||
InvertBoth
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(banjo::CameraInvertMode, {
|
||||
{banjo::CameraInvertMode::InvertNone, "InvertNone"},
|
||||
{banjo::CameraInvertMode::InvertX, "InvertX"},
|
||||
{banjo::CameraInvertMode::InvertY, "InvertY"},
|
||||
{banjo::CameraInvertMode::InvertBoth, "InvertBoth"}
|
||||
});
|
||||
|
||||
CameraInvertMode get_camera_invert_mode();
|
||||
void set_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
CameraInvertMode get_analog_camera_invert_mode();
|
||||
void set_analog_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
enum class AnalogCamMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(banjo::AnalogCamMode, {
|
||||
{banjo::AnalogCamMode::On, "On"},
|
||||
{banjo::AnalogCamMode::Off, "Off"}
|
||||
});
|
||||
|
||||
AnalogCamMode get_analog_cam_mode();
|
||||
void set_analog_cam_mode(AnalogCamMode mode);
|
||||
bool get_analog_cam_mode();
|
||||
|
||||
void open_quit_game_prompt();
|
||||
};
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
#define __BANJO_SOUND_H__
|
||||
|
||||
namespace banjo {
|
||||
void reset_sound_settings();
|
||||
void set_main_volume(int volume);
|
||||
int get_main_volume();
|
||||
void set_bgm_volume(int volume);
|
||||
int get_bgm_volume();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
#ifndef __RECOMP_INPUT_H__
|
||||
#define __RECOMP_INPUT_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "ultramodern/input.hpp"
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "chrono"
|
||||
|
||||
namespace recomp {
|
||||
// x-macros to build input enums and arrays.
|
||||
// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name.
|
||||
// TODO refactor this to allow projects to rename these, or get rid of the readable name and leave that up to individual projects to map.
|
||||
#define DEFINE_N64_BUTTON_INPUTS() \
|
||||
DEFINE_INPUT(A, 0x8000, "A") \
|
||||
DEFINE_INPUT(B, 0x4000, "B") \
|
||||
DEFINE_INPUT(Z, 0x2000, "Z") \
|
||||
DEFINE_INPUT(START, 0x1000, "Start") \
|
||||
DEFINE_INPUT(L, 0x0020, "L") \
|
||||
DEFINE_INPUT(R, 0x0010, "R") \
|
||||
DEFINE_INPUT(C_UP, 0x0008, "C Up") \
|
||||
DEFINE_INPUT(C_LEFT, 0x0002, "C Left") \
|
||||
DEFINE_INPUT(C_DOWN, 0x0004, "C Down") \
|
||||
DEFINE_INPUT(C_RIGHT, 0x0001, "C Right") \
|
||||
DEFINE_INPUT(DPAD_UP, 0x0800, "D Pad Down") \
|
||||
DEFINE_INPUT(DPAD_RIGHT, 0x0100, "D-Pad Down") \
|
||||
DEFINE_INPUT(DPAD_DOWN, 0x0400, "D-Pad Down") \
|
||||
DEFINE_INPUT(DPAD_LEFT, 0x0200, "D-Pad Down")
|
||||
|
||||
#define DEFINE_N64_AXIS_INPUTS() \
|
||||
DEFINE_INPUT(Y_AXIS_POS, 0, "Up") \
|
||||
DEFINE_INPUT(Y_AXIS_NEG, 0, "Down") \
|
||||
DEFINE_INPUT(X_AXIS_NEG, 0, "Left") \
|
||||
DEFINE_INPUT(X_AXIS_POS, 0, "Right") \
|
||||
|
||||
#define DEFINE_RECOMP_UI_INPUTS() \
|
||||
DEFINE_INPUT(TOGGLE_MENU, 0, "Toggle Menu") \
|
||||
DEFINE_INPUT(ACCEPT_MENU, 0, "Accept (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() \
|
||||
DEFINE_N64_AXIS_INPUTS() \
|
||||
DEFINE_RECOMP_UI_INPUTS()
|
||||
|
||||
// Enum containing every recomp input.
|
||||
#define DEFINE_INPUT(name, value, readable) name,
|
||||
enum class GameInput {
|
||||
DEFINE_ALL_INPUTS()
|
||||
|
||||
COUNT,
|
||||
N64_BUTTON_START = A,
|
||||
N64_BUTTON_COUNT = C_RIGHT - N64_BUTTON_START + 1,
|
||||
N64_AXIS_START = X_AXIS_NEG,
|
||||
N64_AXIS_COUNT = Y_AXIS_POS - N64_AXIS_START + 1,
|
||||
};
|
||||
#undef DEFINE_INPUT
|
||||
|
||||
// What type of source an input comes from (SDL_Scancode, SDL_GameControllerButton, SDL_GameControllerAxis, SDL_BUTTON, etc.)
|
||||
enum class InputType {
|
||||
None = 0, // Using zero for None ensures that default initialized InputFields are unbound.
|
||||
Keyboard,
|
||||
Mouse,
|
||||
ControllerDigital,
|
||||
ControllerAnalog // Axis input_id values are the SDL value + 1
|
||||
};
|
||||
|
||||
// A single input. Combines the source of the input (see InputType) and a specific key/button/axis.
|
||||
struct InputField {
|
||||
InputType input_type;
|
||||
// Represents a single source input. e.g. A keyboard's shift key, or a controller's R trigger
|
||||
int32_t input_id;
|
||||
std::string to_string() const;
|
||||
auto operator<=>(const InputField& rhs) const = default;
|
||||
};
|
||||
|
||||
void poll_inputs();
|
||||
float get_input_analog(int controller_num, const InputField& field);
|
||||
float get_input_analog(int controller_num, const std::span<const recomp::InputField> fields);
|
||||
bool get_input_digital(int controller_num, const InputField& field);
|
||||
bool get_input_digital(int controller_num, const std::span<const recomp::InputField> fields);
|
||||
void get_gyro_deltas(float* x, float* y);
|
||||
void get_mouse_deltas(float* x, float* y);
|
||||
void get_right_analog(int controller_num, float* x, float* y);
|
||||
|
||||
enum class InputDevice {
|
||||
Controller,
|
||||
Keyboard,
|
||||
COUNT
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::InputDevice, {
|
||||
{ recomp::InputDevice::Controller, "Controller" },
|
||||
{ recomp::InputDevice::Keyboard, "Keyboard" },
|
||||
});
|
||||
|
||||
void config_menu_set_cont_or_kb(bool cont_interacted);
|
||||
|
||||
struct DefaultN64Mappings {
|
||||
std::vector<InputField> a;
|
||||
std::vector<InputField> b;
|
||||
std::vector<InputField> l;
|
||||
std::vector<InputField> r;
|
||||
std::vector<InputField> z;
|
||||
std::vector<InputField> start;
|
||||
|
||||
std::vector<InputField> c_left;
|
||||
std::vector<InputField> c_right;
|
||||
std::vector<InputField> c_up;
|
||||
std::vector<InputField> c_down;
|
||||
|
||||
std::vector<InputField> dpad_left;
|
||||
std::vector<InputField> dpad_right;
|
||||
std::vector<InputField> dpad_up;
|
||||
std::vector<InputField> dpad_down;
|
||||
|
||||
std::vector<InputField> analog_left;
|
||||
std::vector<InputField> analog_right;
|
||||
std::vector<InputField> analog_up;
|
||||
std::vector<InputField> analog_down;
|
||||
|
||||
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) {
|
||||
static const std::vector<InputField> empty_input_field{};
|
||||
switch (input) {
|
||||
case GameInput::A: return defaults.a;
|
||||
case GameInput::B: return defaults.b;
|
||||
case GameInput::L: return defaults.l;
|
||||
case GameInput::R: return defaults.r;
|
||||
case GameInput::Z: return defaults.z;
|
||||
case GameInput::START: return defaults.start;
|
||||
case GameInput::C_LEFT: return defaults.c_left;
|
||||
case GameInput::C_RIGHT: return defaults.c_right;
|
||||
case GameInput::C_UP: return defaults.c_up;
|
||||
case GameInput::C_DOWN: return defaults.c_down;
|
||||
case GameInput::DPAD_LEFT: return defaults.dpad_left;
|
||||
case GameInput::DPAD_RIGHT: return defaults.dpad_right;
|
||||
case GameInput::DPAD_UP: return defaults.dpad_up;
|
||||
case GameInput::DPAD_DOWN: return defaults.dpad_down;
|
||||
case GameInput::X_AXIS_NEG: return defaults.analog_left;
|
||||
case GameInput::X_AXIS_POS: return defaults.analog_right;
|
||||
case GameInput::Y_AXIS_POS: return defaults.analog_up;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
extern const DefaultN64Mappings default_n64_keyboard_mappings;
|
||||
extern const DefaultN64Mappings default_n64_controller_mappings;
|
||||
|
||||
constexpr size_t bindings_per_input = 2;
|
||||
|
||||
size_t get_num_inputs();
|
||||
const std::string& get_input_name(GameInput input);
|
||||
const std::string& get_input_enum_name(GameInput input);
|
||||
GameInput get_input_from_enum_name(const std::string_view name);
|
||||
InputField& get_input_binding(int profile_index, GameInput input, size_t binding_index);
|
||||
void set_input_binding(int profile_index, GameInput input, size_t binding_index, InputField value);
|
||||
void clear_input_binding(int profile_index, GameInput input);
|
||||
void reset_input_binding(int profile_index, InputDevice device, GameInput input);
|
||||
void reset_profile_bindings(int profile_index, recomp::InputDevice device);
|
||||
int add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom);
|
||||
int get_input_profile_by_key(const std::string &key);
|
||||
const std::string &get_input_profile_key(int profile_index);
|
||||
const std::string &get_input_profile_name(int profile_index);
|
||||
InputDevice get_input_profile_device(int profile_index);
|
||||
bool is_input_profile_custom(int profile_index);
|
||||
int get_input_profile_count();
|
||||
const std::vector<int> get_indices_for_custom_profiles(InputDevice device);
|
||||
void set_input_profile_for_player(int player_index, int profile_index, InputDevice device);
|
||||
int get_input_profile_for_player(int player_index, InputDevice device);
|
||||
|
||||
struct ControllerGUID {
|
||||
uint64_t hash;
|
||||
std::string serial;
|
||||
int vendor{};
|
||||
int product{};
|
||||
int version{};
|
||||
int crc16{};
|
||||
};
|
||||
|
||||
int add_controller(ControllerGUID guid, int profile_index);
|
||||
const ControllerGUID &get_controller_guid(int controller_index);
|
||||
int get_controller_profile_index(int controller_index);
|
||||
int get_controller_by_guid(ControllerGUID guid);
|
||||
int get_controller_count();
|
||||
ControllerGUID get_guid_from_sdl_controller(SDL_GameController* game_controller);
|
||||
int get_controller_profile_index_from_sdl_controller(SDL_GameController* game_controller);
|
||||
std::string get_string_from_controller_guid(ControllerGUID guid);
|
||||
|
||||
void initialize_input_bindings();
|
||||
int get_sp_controller_profile_index();
|
||||
int get_sp_keyboard_profile_index();
|
||||
int get_mp_keyboard_profile_index(int player_index);
|
||||
|
||||
bool get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out);
|
||||
void set_rumble(int player_index, bool);
|
||||
void update_rumble();
|
||||
void handle_events();
|
||||
|
||||
ultramodern::input::connected_device_info_t get_connected_device_info(int controller_num);
|
||||
|
||||
// Rumble strength ranges from 0 to 100.
|
||||
int get_rumble_strength();
|
||||
void set_rumble_strength(int strength);
|
||||
|
||||
// Gyro and mouse sensitivities range from 0 to 100.
|
||||
int get_gyro_sensitivity();
|
||||
int get_mouse_sensitivity();
|
||||
int get_joystick_deadzone();
|
||||
void set_gyro_sensitivity(int strength);
|
||||
void set_mouse_sensitivity(int strength);
|
||||
void set_joystick_deadzone(int strength);
|
||||
void apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out);
|
||||
void set_right_analog_suppressed(bool suppressed);
|
||||
|
||||
enum class BackgroundInputMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::BackgroundInputMode, {
|
||||
{recomp::BackgroundInputMode::On, "On"},
|
||||
{recomp::BackgroundInputMode::Off, "Off"}
|
||||
});
|
||||
|
||||
BackgroundInputMode get_background_input_mode();
|
||||
void set_background_input_mode(BackgroundInputMode mode);
|
||||
|
||||
bool get_single_controller_mode();
|
||||
void set_single_controller_mode(bool single_controller);
|
||||
|
||||
bool game_input_disabled();
|
||||
bool all_input_disabled();
|
||||
|
||||
}
|
||||
|
||||
namespace recompinput {
|
||||
struct BindingState {
|
||||
bool active = false;
|
||||
// Designates when binding has been cancelled or completed and the event queue should be purged/ignored.
|
||||
bool skip_events = false;
|
||||
int player_index = -1;
|
||||
recomp::GameInput game_input = recomp::GameInput::COUNT;
|
||||
int binding_index = -1;
|
||||
recomp::InputField new_binding = {};
|
||||
recomp::InputDevice device = recomp::InputDevice::COUNT;
|
||||
};
|
||||
|
||||
void start_scanning_for_binding(int player_index, recomp::GameInput game_input, int binding_index, recomp::InputDevice device);
|
||||
void stop_scanning_for_binding();
|
||||
bool is_binding();
|
||||
|
||||
BindingState& get_binding_state();
|
||||
|
||||
int get_num_players();
|
||||
|
||||
struct AssignedPlayer {
|
||||
SDL_GameController* controller = nullptr;
|
||||
bool is_assigned = false;
|
||||
bool keyboard_enabled = false;
|
||||
std::chrono::high_resolution_clock::duration last_button_press_timestamp = std::chrono::high_resolution_clock::duration::zero();
|
||||
|
||||
AssignedPlayer() : controller(nullptr), is_assigned(false), keyboard_enabled(false), last_button_press_timestamp(std::chrono::high_resolution_clock::duration::zero()) {};
|
||||
};
|
||||
|
||||
constexpr size_t temp_max_players = 4;
|
||||
|
||||
struct PlayerAssignmentState {
|
||||
bool is_assigning = false;
|
||||
int player_index = 0;
|
||||
std::array<AssignedPlayer, temp_max_players> temp_assigned_players;
|
||||
|
||||
PlayerAssignmentState() : is_assigning(false), player_index(0), temp_assigned_players{} {};
|
||||
};
|
||||
|
||||
AssignedPlayer& get_assigned_player(int player_index, bool temp_player = false);
|
||||
|
||||
bool get_player_is_assigned(int player_index);
|
||||
recomp::InputDevice get_assigned_player_input_device(int player_index);
|
||||
void start_player_assignment(void);
|
||||
void stop_player_assignment(void);
|
||||
void stop_player_assignment_and_close_modal(void);
|
||||
void commit_player_assignment(void);
|
||||
bool is_player_assignment_active();
|
||||
std::chrono::steady_clock::duration get_player_time_since_last_button_press(int player_index);
|
||||
|
||||
bool does_player_have_controller(int player_index);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,175 +0,0 @@
|
||||
#ifndef __RECOMP_UI__
|
||||
#define __RECOMP_UI__
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <list>
|
||||
|
||||
// TODO move this file into src/ui
|
||||
|
||||
#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"
|
||||
|
||||
namespace Rml {
|
||||
class ElementDocument;
|
||||
class EventListenerInstancer;
|
||||
class Context;
|
||||
class Event;
|
||||
}
|
||||
|
||||
namespace recompui {
|
||||
class UiEventListenerInstancer;
|
||||
|
||||
// TODO remove this once the UI has been ported over to the new system.
|
||||
class MenuController {
|
||||
public:
|
||||
virtual ~MenuController() {}
|
||||
virtual void load_document() = 0;
|
||||
virtual void register_events(UiEventListenerInstancer& listener) = 0;
|
||||
virtual void make_bindings(Rml::Context* context) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<MenuController> create_launcher_menu();
|
||||
std::unique_ptr<MenuController> create_config_menu();
|
||||
|
||||
using event_handler_t = void(const std::string& param, Rml::Event&);
|
||||
|
||||
void queue_event(const SDL_Event& event);
|
||||
bool try_deque_event(SDL_Event& out);
|
||||
|
||||
std::unique_ptr<UiEventListenerInstancer> make_event_listener_instancer();
|
||||
void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler);
|
||||
|
||||
void show_context(ContextId context, std::string_view param);
|
||||
void hide_context(ContextId context);
|
||||
void hide_all_contexts();
|
||||
bool is_context_shown(ContextId context);
|
||||
bool is_context_capturing_input();
|
||||
bool is_context_capturing_mouse();
|
||||
bool is_any_context_shown();
|
||||
ContextId try_close_current_context();
|
||||
|
||||
ContextId get_launcher_context_id();
|
||||
ContextId get_config_context_id();
|
||||
ContextId get_config_sub_menu_context_id();
|
||||
|
||||
enum class ConfigTabId {
|
||||
General,
|
||||
Controls,
|
||||
Graphics,
|
||||
Sound,
|
||||
Mods,
|
||||
Debug,
|
||||
};
|
||||
|
||||
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();
|
||||
void focus_mod_configure_button();
|
||||
|
||||
void init_styling(const std::filesystem::path& rcss_file);
|
||||
void init_prompt_context();
|
||||
void open_choice_prompt(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& confirm_label_text,
|
||||
const std::string& cancel_label_text,
|
||||
std::function<void()> confirm_action,
|
||||
std::function<void()> cancel_action,
|
||||
ButtonStyle confirm_variant = ButtonStyle::Success,
|
||||
ButtonStyle cancel_variant = ButtonStyle::Danger,
|
||||
bool focus_on_cancel = true,
|
||||
const std::string& return_element_id = ""
|
||||
);
|
||||
void open_info_prompt(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& okay_label_text,
|
||||
std::function<void()> okay_action,
|
||||
ButtonStyle okay_variant = ButtonStyle::Danger,
|
||||
const std::string& return_element_id = ""
|
||||
);
|
||||
void open_notification(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& return_element_id = ""
|
||||
);
|
||||
void close_prompt();
|
||||
bool is_prompt_open();
|
||||
void update_mod_list(bool scan_mods = true);
|
||||
void process_game_started();
|
||||
|
||||
void apply_color_hack();
|
||||
void get_window_size(int& width, int& height);
|
||||
void set_cursor_visible(bool visible);
|
||||
void update_supported_options();
|
||||
void toggle_fullscreen();
|
||||
|
||||
bool get_cont_active(void);
|
||||
void set_cont_active(bool active);
|
||||
void activate_mouse();
|
||||
|
||||
void message_box(const char* msg);
|
||||
|
||||
void set_render_hooks();
|
||||
|
||||
Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag);
|
||||
Rml::ElementDocument* load_document(const std::filesystem::path& path);
|
||||
Rml::ElementDocument* create_empty_document();
|
||||
Rml::Element* get_child_by_tag(Rml::Element* parent, const std::string& tag);
|
||||
|
||||
void queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height);
|
||||
void queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes);
|
||||
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: d46ed007a5...995cdf2ad4
+73
-554
@@ -1,10 +1,14 @@
|
||||
#include "banjo_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recompui/recompui.h"
|
||||
#include "recompui/config.h"
|
||||
#include "recompinput/recompinput.h"
|
||||
#include "banjo_sound.h"
|
||||
#include "banjo_render.h"
|
||||
#include "banjo_support.h"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "librecomp/files.hpp"
|
||||
#include "librecomp/config.hpp"
|
||||
#include "util/file.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
@@ -18,584 +22,99 @@
|
||||
#include "apple/rt64_apple.h"
|
||||
#endif
|
||||
|
||||
constexpr std::u8string_view general_filename = u8"general.json";
|
||||
constexpr std::u8string_view graphics_filename = u8"graphics.json";
|
||||
constexpr std::u8string_view controls_filename = u8"controls.json";
|
||||
constexpr std::u8string_view sound_filename = u8"sound.json";
|
||||
static void add_general_options(recomp::config::Config &config) {
|
||||
using EnumOptionVector = const std::vector<recomp::config::ConfigOptionEnumOption>;
|
||||
|
||||
constexpr auto res_default = ultramodern::renderer::Resolution::Auto;
|
||||
constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9;
|
||||
constexpr auto api_default = ultramodern::renderer::GraphicsApi::Auto;
|
||||
constexpr auto ar_default = ultramodern::renderer::AspectRatio::Expand;
|
||||
constexpr auto msaa_default = ultramodern::renderer::Antialiasing::MSAA2X;
|
||||
constexpr auto rr_default = ultramodern::renderer::RefreshRate::Display;
|
||||
constexpr auto hpfb_default = ultramodern::renderer::HighPrecisionFramebuffer::Off;
|
||||
constexpr int ds_default = 1;
|
||||
constexpr int rr_manual_default = 60;
|
||||
constexpr bool developer_mode_default = false;
|
||||
static EnumOptionVector camera_invert_mode_options = {
|
||||
{banjo::CameraInvertMode::InvertNone, "InvertNone", "None"},
|
||||
{banjo::CameraInvertMode::InvertX, "InvertX", "Invert X"},
|
||||
{banjo::CameraInvertMode::InvertY, "InvertY", "Invert Y"},
|
||||
{banjo::CameraInvertMode::InvertBoth, "InvertBoth", "Invert Both"}
|
||||
};
|
||||
config.add_enum_option(
|
||||
banjo::configkeys::general::camera_invert_mode,
|
||||
"Aiming Camera Mode",
|
||||
// TODO: Update for banjo
|
||||
"Inverts the camera controls. <recomp-color primary>Invert Y</recomp-color> is the default and matches the original game.",
|
||||
camera_invert_mode_options,
|
||||
banjo::CameraInvertMode::InvertY
|
||||
);
|
||||
|
||||
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
|
||||
|
||||
template <typename T>
|
||||
T from_or_default(const json& j, const std::string& key, T default_value) {
|
||||
T ret;
|
||||
auto find_it = j.find(key);
|
||||
if (find_it != j.end()) {
|
||||
find_it->get_to(ret);
|
||||
}
|
||||
else {
|
||||
ret = default_value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void call_if_key_exists(void (*func)(T), const json& j, const std::string& key) {
|
||||
auto find_it = j.find(key);
|
||||
if (find_it != j.end()) {
|
||||
T val;
|
||||
find_it->get_to(val);
|
||||
func(val);
|
||||
}
|
||||
}
|
||||
|
||||
namespace ultramodern {
|
||||
void to_json(json& j, const renderer::GraphicsConfig& config) {
|
||||
j = json{
|
||||
{"res_option", config.res_option},
|
||||
{"wm_option", config.wm_option},
|
||||
{"hr_option", config.hr_option},
|
||||
{"api_option", config.api_option},
|
||||
{"ds_option", config.ds_option},
|
||||
{"ar_option", config.ar_option},
|
||||
{"msaa_option", config.msaa_option},
|
||||
{"rr_option", config.rr_option},
|
||||
{"hpfb_option", config.hpfb_option},
|
||||
{"rr_manual_value", config.rr_manual_value},
|
||||
{"developer_mode", config.developer_mode},
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, renderer::GraphicsConfig& config) {
|
||||
config.res_option = from_or_default(j, "res_option", res_default);
|
||||
config.wm_option = from_or_default(j, "wm_option", wm_default());
|
||||
config.hr_option = from_or_default(j, "hr_option", hr_default);
|
||||
config.api_option = from_or_default(j, "api_option", api_default);
|
||||
config.ds_option = from_or_default(j, "ds_option", ds_default);
|
||||
config.ar_option = from_or_default(j, "ar_option", ar_default);
|
||||
config.msaa_option = from_or_default(j, "msaa_option", msaa_default);
|
||||
config.rr_option = from_or_default(j, "rr_option", rr_default);
|
||||
config.hpfb_option = from_or_default(j, "hpfb_option", hpfb_default);
|
||||
config.rr_manual_value = from_or_default(j, "rr_manual_value", rr_manual_default);
|
||||
config.developer_mode = from_or_default(j, "developer_mode", developer_mode_default);
|
||||
}
|
||||
}
|
||||
|
||||
namespace recomp {
|
||||
void to_json(json& j, const InputField& field) {
|
||||
j = json{ {"input_type", field.input_type}, {"input_id", field.input_id} };
|
||||
}
|
||||
|
||||
void from_json(const json& j, InputField& field) {
|
||||
j.at("input_type").get_to(field.input_type);
|
||||
j.at("input_id").get_to(field.input_id);
|
||||
}
|
||||
|
||||
void to_json(json& j, const ControllerGUID& guid) {
|
||||
j = json{ {"serial", guid.serial}, {"vendor", guid.vendor}, {"product", guid.product}, {"version", guid.version}, {"crc16", guid.crc16} };
|
||||
}
|
||||
|
||||
void from_json(const json& j, ControllerGUID& guid) {
|
||||
j.at("serial").get_to(guid.serial);
|
||||
j.at("vendor").get_to(guid.vendor);
|
||||
j.at("product").get_to(guid.product);
|
||||
j.at("version").get_to(guid.version);
|
||||
j.at("crc16").get_to(guid.crc16);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path banjo::get_app_folder_path() {
|
||||
// directly check for portable.txt (windows and native linux binary)
|
||||
if (std::filesystem::exists("portable.txt")) {
|
||||
return std::filesystem::current_path();
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// Check for portable file in the directory containing the app bundle.
|
||||
const auto app_bundle_path = banjo::get_bundle_directory().parent_path();
|
||||
if (std::filesystem::exists(app_bundle_path / "portable.txt")) {
|
||||
return app_bundle_path;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::filesystem::path recomp_dir{};
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Deduce local app data path.
|
||||
PWSTR known_path = NULL;
|
||||
HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path);
|
||||
if (result == S_OK) {
|
||||
recomp_dir = std::filesystem::path{known_path} / banjo::program_id;
|
||||
}
|
||||
|
||||
CoTaskMemFree(known_path);
|
||||
#elif defined(__linux__) || defined(__APPLE__)
|
||||
// check for APP_FOLDER_PATH env var
|
||||
if (getenv("APP_FOLDER_PATH") != nullptr) {
|
||||
return std::filesystem::path{getenv("APP_FOLDER_PATH")};
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
const auto supportdir = banjo::get_application_support_directory();
|
||||
if (supportdir) {
|
||||
return *supportdir / banjo::program_id;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *homedir;
|
||||
|
||||
if ((homedir = getenv("HOME")) == nullptr) {
|
||||
#if defined(__linux__)
|
||||
homedir = getpwuid(getuid())->pw_dir;
|
||||
#elif defined(__APPLE__)
|
||||
homedir = GetHomeDirectory();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (homedir != nullptr) {
|
||||
recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{banjo::program_id});
|
||||
}
|
||||
#endif
|
||||
|
||||
return recomp_dir;
|
||||
}
|
||||
|
||||
bool read_json(std::ifstream input_file, nlohmann::json& json_out) {
|
||||
if (!input_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
input_file >> json_out;
|
||||
}
|
||||
catch (nlohmann::json::parse_error&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Try reading and parsing the backup file.
|
||||
if (read_json(recomp::open_input_backup_file(path), json_out)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Both reads failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
output_file << std::setw(4) << json_data;
|
||||
}
|
||||
return recomp::finalize_output_file_with_backup(path);
|
||||
}
|
||||
|
||||
bool save_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode());
|
||||
config_json["rumble_strength"] = recomp::get_rumble_strength();
|
||||
config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity();
|
||||
config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity();
|
||||
config_json["joystick_deadzone"] = recomp::get_joystick_deadzone();
|
||||
config_json["camera_invert_mode"] = banjo::get_camera_invert_mode();
|
||||
config_json["analog_cam_mode"] = banjo::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = banjo::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = banjo::get_debug_mode_enabled();
|
||||
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
recomp::set_background_input_mode(from_or_default(config_json, "background_input_mode", recomp::BackgroundInputMode::On));
|
||||
recomp::set_rumble_strength(from_or_default(config_json, "rumble_strength", 25));
|
||||
recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50));
|
||||
recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", is_steam_deck ? 50 : 0));
|
||||
recomp::set_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5));
|
||||
banjo::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", banjo::CameraInvertMode::InvertY));
|
||||
banjo::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", banjo::AnalogCamMode::Off));
|
||||
banjo::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", banjo::CameraInvertMode::InvertNone));
|
||||
banjo::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
}
|
||||
|
||||
bool load_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_general_settings_from_json(config_json);
|
||||
return true;
|
||||
}
|
||||
|
||||
void assign_mapping(int profile_index, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
for (size_t binding_index = 0; binding_index < std::min(value.size(), recomp::bindings_per_input); binding_index++) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]);
|
||||
}
|
||||
};
|
||||
|
||||
// same as assign_mapping, except will clear unassigned bindings if not in value
|
||||
void assign_mapping_complete(int profile_index, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
if (binding_index >= value.size()) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
} else {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void assign_all_mappings(int profile_index, const recomp::DefaultN64Mappings& values) {
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::A, values.a);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::B, values.b);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::Z, values.z);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::START, values.start);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_UP, values.dpad_up);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_DOWN, values.dpad_down);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_LEFT, values.dpad_left);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_RIGHT, values.dpad_right);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::L, values.l);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::R, values.r);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_UP, values.c_up);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_DOWN, values.c_down);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_LEFT, values.c_left);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::C_RIGHT, values.c_right);
|
||||
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_NEG, values.analog_left);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_POS, values.analog_right);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_NEG, values.analog_down);
|
||||
assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_POS, values.analog_up);
|
||||
|
||||
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() {
|
||||
assign_all_mappings(recomp::get_sp_keyboard_profile_index(), recomp::default_n64_keyboard_mappings);
|
||||
assign_all_mappings(recomp::get_sp_controller_profile_index(), recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void banjo::reset_cont_input_bindings(int profile_index) {
|
||||
assign_all_mappings(profile_index, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void banjo::reset_kb_input_bindings(int profile_index) {
|
||||
assign_all_mappings(profile_index, recomp::default_n64_keyboard_mappings);
|
||||
}
|
||||
|
||||
void banjo::reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input) {
|
||||
assign_mapping_complete(
|
||||
profile_index,
|
||||
input,
|
||||
recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
input
|
||||
)
|
||||
config.add_bool_option(
|
||||
banjo::configkeys::general::analog_cam_mode,
|
||||
"Analog Camera",
|
||||
// TODO: Update for banjo
|
||||
"Enables the analog camera.",
|
||||
false
|
||||
);
|
||||
config.add_enum_option(
|
||||
banjo::configkeys::general::analog_camera_invert_mode,
|
||||
"Analog Camera Mode",
|
||||
// TODO: Update for banjo
|
||||
"Inverts the camera controls for the analog camera if it's enabled. <recomp-color primary>None</recomp-color> is the default.",
|
||||
camera_invert_mode_options,
|
||||
banjo::CameraInvertMode::InvertNone
|
||||
);
|
||||
config.add_option_disable_dependency(
|
||||
banjo::configkeys::general::analog_camera_invert_mode,
|
||||
banjo::configkeys::general::analog_cam_mode,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
void reset_graphics_options() {
|
||||
ultramodern::renderer::GraphicsConfig new_config{};
|
||||
new_config.res_option = res_default;
|
||||
new_config.wm_option = wm_default();
|
||||
new_config.hr_option = hr_default;
|
||||
new_config.ds_option = ds_default;
|
||||
new_config.ar_option = ar_default;
|
||||
new_config.msaa_option = msaa_default;
|
||||
new_config.rr_option = rr_default;
|
||||
new_config.hpfb_option = hpfb_default;
|
||||
new_config.rr_manual_value = rr_manual_default;
|
||||
new_config.developer_mode = developer_mode_default;
|
||||
ultramodern::renderer::set_graphics_config(new_config);
|
||||
template <typename T = uint32_t>
|
||||
T get_general_config_enum_value(const std::string& option_id) {
|
||||
return static_cast<T>(std::get<uint32_t>(recompui::config::get_general_config().get_option_value(option_id)));
|
||||
}
|
||||
|
||||
bool save_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
ultramodern::to_json(config_json, ultramodern::renderer::get_graphics_config());
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
banjo::CameraInvertMode banjo::get_camera_invert_mode() {
|
||||
return get_general_config_enum_value<banjo::CameraInvertMode>(banjo::configkeys::general::camera_invert_mode);
|
||||
}
|
||||
|
||||
bool load_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ultramodern::renderer::GraphicsConfig new_config{};
|
||||
ultramodern::from_json(config_json, new_config);
|
||||
ultramodern::renderer::set_graphics_config(new_config);
|
||||
return true;
|
||||
banjo::CameraInvertMode banjo::get_analog_camera_invert_mode() {
|
||||
return get_general_config_enum_value<banjo::CameraInvertMode>(banjo::configkeys::general::analog_camera_invert_mode);
|
||||
}
|
||||
|
||||
void add_input_bindings(nlohmann::json& out, int profile_index, recomp::GameInput input) {
|
||||
const std::string& input_name = recomp::get_input_enum_name(input);
|
||||
nlohmann::json& out_array = out[input_name];
|
||||
out_array = nlohmann::json::array();
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
out_array[binding_index] = recomp::get_input_binding(profile_index, input, binding_index);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr int controls_version = 3;
|
||||
|
||||
bool save_controls_config(const std::filesystem::path& path) {
|
||||
int profile_count = recomp::get_input_profile_count();
|
||||
int controller_count = recomp::get_controller_count();
|
||||
nlohmann::json config_json{};
|
||||
config_json["version"] = controls_version;
|
||||
config_json["profiles"] = std::vector<nlohmann::json>(profile_count);
|
||||
config_json["controllers"] = std::vector<nlohmann::json>(controller_count);
|
||||
|
||||
nlohmann::json &profiles = config_json["profiles"];
|
||||
for (int i = 0; i < profile_count; i++) {
|
||||
nlohmann::json &profile = profiles[i];
|
||||
profile["key"] = recomp::get_input_profile_key(i);
|
||||
profile["name"] = recomp::get_input_profile_name(i);
|
||||
profile["device"] = recomp::get_input_profile_device(i);
|
||||
profile["custom"] = recomp::is_input_profile_custom(i);
|
||||
profile["mappings"] = nlohmann::json();
|
||||
|
||||
for (int j = 0; j < (int)(recomp::GameInput::COUNT); j++) {
|
||||
add_input_bindings(profile["mappings"], i, (recomp::GameInput)(j));
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json &controllers = config_json["controllers"];
|
||||
for (int i = 0; i < controller_count; i++) {
|
||||
nlohmann::json &controller = controllers[i];
|
||||
controller["guid"] = recomp::get_controller_guid(i);
|
||||
controller["profile"] = recomp::get_input_profile_key(recomp::get_controller_profile_index(i));
|
||||
}
|
||||
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
bool banjo::get_analog_cam_mode() {
|
||||
return std::get<bool>(recompui::config::get_general_config().get_option_value(banjo::configkeys::general::analog_cam_mode));
|
||||
}
|
||||
|
||||
bool load_input_device_from_json(const nlohmann::json& config_json, int profile_index, recomp::InputDevice device, const std::string& key) {
|
||||
// Check if the json object for the given key exists.
|
||||
auto find_it = config_json.find(key);
|
||||
if (find_it == config_json.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nlohmann::json& mappings_json = *find_it;
|
||||
|
||||
for (size_t i = 0; i < recomp::get_num_inputs(); i++) {
|
||||
recomp::GameInput cur_input = static_cast<recomp::GameInput>(i);
|
||||
const std::string& input_name = recomp::get_input_enum_name(cur_input);
|
||||
|
||||
// Check if the json object for the given input exists and that it's an array.
|
||||
auto find_input_it = mappings_json.find(input_name);
|
||||
if (find_input_it == mappings_json.end() || !find_input_it->is_array()) {
|
||||
assign_mapping(
|
||||
profile_index,
|
||||
cur_input,
|
||||
recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
cur_input
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const nlohmann::json& input_json = *find_input_it;
|
||||
|
||||
// Deserialize all the bindings from the json array (up to the max number of bindings per input).
|
||||
for (size_t binding_index = 0; binding_index < std::min(recomp::bindings_per_input, input_json.size()); binding_index++) {
|
||||
recomp::InputField cur_field{};
|
||||
recomp::from_json(input_json[binding_index], cur_field);
|
||||
recomp::set_input_binding(profile_index, cur_input, binding_index, cur_field);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
static void add_sound_options(recomp::config::Config &config) {
|
||||
config.add_percent_number_option(
|
||||
banjo::configkeys::sound::bgm_volume,
|
||||
"Background Music Volume",
|
||||
"Controls the overall volume of background music.",
|
||||
100.0f
|
||||
);
|
||||
}
|
||||
template <typename T = uint32_t>
|
||||
T get_sound_config_number_value(const std::string& option_id) {
|
||||
return static_cast<T>(std::get<double>(recompui::config::get_sound_config().get_option_value(option_id)));
|
||||
}
|
||||
|
||||
bool load_controls_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto version_it = config_json.find("version");
|
||||
if (version_it != config_json.end()) {
|
||||
auto profiles = config_json.find("profiles");
|
||||
if (profiles == config_json.end() || !profiles->is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const nlohmann::json &profile : *profiles) {
|
||||
std::string key = profile.value("key", std::string());
|
||||
std::string name = profile.value("name", std::string());
|
||||
recomp::InputDevice device = profile.value("device", recomp::InputDevice::COUNT);
|
||||
bool custom = profile.value("custom", false);
|
||||
if (!key.empty() && !name.empty() && device != recomp::InputDevice::COUNT) {
|
||||
int profile_index = recomp::add_input_profile(key, name, device, custom);
|
||||
if (!load_input_device_from_json(profile, profile_index, device, "mappings")) {
|
||||
assign_all_mappings(profile_index, device == recomp::InputDevice::Keyboard ? recomp::default_n64_keyboard_mappings : recomp::default_n64_controller_mappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto controllers = config_json.find("controllers");
|
||||
if (controllers == config_json.end() || !controllers->is_array()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const nlohmann::json &controller : *controllers) {
|
||||
auto guid = controller.find("guid");
|
||||
auto profile = controller.find("profile");
|
||||
if (guid != controller.end() && guid->is_object() && profile != controller.end() && profile->is_string()) {
|
||||
int profile_index = recomp::get_input_profile_by_key(*profile);
|
||||
if (profile_index >= 0) {
|
||||
recomp::add_controller(*guid, profile_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Version 1 of the format only had bindings for Player 1 on the root element.
|
||||
if (!load_input_device_from_json(config_json, recomp::get_sp_keyboard_profile_index(), recomp::InputDevice::Keyboard, "keyboard")) {
|
||||
assign_all_mappings(recomp::get_sp_keyboard_profile_index(), recomp::default_n64_keyboard_mappings);
|
||||
}
|
||||
|
||||
if (!load_input_device_from_json(config_json, recomp::get_sp_controller_profile_index(), recomp::InputDevice::Controller, "controller")) {
|
||||
assign_all_mappings(recomp::get_sp_controller_profile_index(), recomp::default_n64_controller_mappings);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
int banjo::get_bgm_volume() {
|
||||
return get_sound_config_number_value<int>(banjo::configkeys::sound::bgm_volume);
|
||||
}
|
||||
|
||||
bool save_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["main_volume"] = banjo::get_main_volume();
|
||||
config_json["bgm_volume"] = banjo::get_bgm_volume();
|
||||
|
||||
return banjo::save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
bool load_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!banjo::read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
banjo::reset_sound_settings();
|
||||
call_if_key_exists(banjo::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(banjo::set_bgm_volume, config_json, "bgm_volume");
|
||||
return true;
|
||||
}
|
||||
|
||||
void banjo::load_config() {
|
||||
detect_steam_deck();
|
||||
|
||||
std::filesystem::path recomp_dir = banjo::get_app_folder_path();
|
||||
std::filesystem::path general_path = recomp_dir / general_filename;
|
||||
std::filesystem::path graphics_path = recomp_dir / graphics_filename;
|
||||
std::filesystem::path controls_path = recomp_dir / controls_filename;
|
||||
std::filesystem::path sound_path = recomp_dir / sound_filename;
|
||||
void banjo::init_config() {
|
||||
std::filesystem::path recomp_dir = recompui::file::get_app_folder_path();
|
||||
|
||||
if (!recomp_dir.empty()) {
|
||||
std::filesystem::create_directories(recomp_dir);
|
||||
}
|
||||
|
||||
// TODO error handling for failing to save config files after resetting them.
|
||||
auto &general_config = recompui::config::create_general_tab();
|
||||
add_general_options(general_config);
|
||||
|
||||
if (!load_general_config(general_path)) {
|
||||
// Set the general settings from an empty json to use defaults.
|
||||
set_general_settings_from_json({});
|
||||
save_general_config(general_path);
|
||||
}
|
||||
auto &graphics_config = recompui::config::create_graphics_tab();
|
||||
|
||||
if (!load_graphics_config(graphics_path)) {
|
||||
reset_graphics_options();
|
||||
save_graphics_config(graphics_path);
|
||||
}
|
||||
recompui::config::create_controls_tab();
|
||||
|
||||
recomp::initialize_input_bindings();
|
||||
auto &sound_config = recompui::config::create_sound_tab();
|
||||
add_sound_options(sound_config);
|
||||
|
||||
if (!load_controls_config(controls_path)) {
|
||||
banjo::reset_input_bindings();
|
||||
save_controls_config(controls_path);
|
||||
}
|
||||
recompui::config::create_mods_tab();
|
||||
|
||||
recompui::config::finalize();
|
||||
|
||||
if (!load_sound_config(sound_path)) {
|
||||
banjo::reset_sound_settings();
|
||||
save_sound_config(sound_path);
|
||||
}
|
||||
}
|
||||
|
||||
void banjo::save_config() {
|
||||
std::filesystem::path recomp_dir = banjo::get_app_folder_path();
|
||||
|
||||
if (recomp_dir.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::create_directories(recomp_dir);
|
||||
|
||||
// TODO error handling for failing to save config files.
|
||||
|
||||
save_general_config(recomp_dir / general_filename);
|
||||
save_graphics_config(recomp_dir / graphics_filename);
|
||||
save_controls_config(recomp_dir / controls_filename);
|
||||
save_sound_config(recomp_dir / sound_filename);
|
||||
}
|
||||
|
||||
@@ -1,472 +0,0 @@
|
||||
#include <array>
|
||||
|
||||
#include "xxHash/xxh3.h"
|
||||
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "recomp_input.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
// Arrays that hold the mappings for every input for keyboard and controller respectively.
|
||||
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
|
||||
using input_mapping_array = std::array<input_mapping, static_cast<size_t>(recomp::GameInput::COUNT)>;
|
||||
|
||||
struct InputProfile {
|
||||
std::string key;
|
||||
std::string name;
|
||||
recomp::InputDevice device;
|
||||
input_mapping_array mappings;
|
||||
bool custom = false;
|
||||
};
|
||||
|
||||
static std::vector<InputProfile> input_profiles{};
|
||||
static std::array<std::pair<int, int>, 4> players_input_profile_indices{};
|
||||
static std::unordered_map<std::string, int> input_profile_key_index_map{};
|
||||
static std::vector<int> input_profile_custom_indices[2]{};
|
||||
|
||||
struct Controller {
|
||||
recomp::ControllerGUID guid;
|
||||
int profile_index;
|
||||
};
|
||||
|
||||
static std::vector<Controller> controllers;
|
||||
static std::unordered_map<uint64_t, int> controller_hash_index_map{};
|
||||
|
||||
static int keyboard_sp_profile_index = -1;
|
||||
static int controller_sp_profile_index = -1;
|
||||
|
||||
static const std::string keyboard_sp_profile_key = "keyboard_sp";
|
||||
static const std::string controller_sp_profile_key = "controller_sp";
|
||||
static const std::string keyboard_sp_profile_name = "Keyboard (SP)";
|
||||
static const std::string controller_sp_profile_name = "Controller (SP)";
|
||||
|
||||
static const std::string keyboard_mp_profile_key = "keyboard_mp_player_"; // + player index
|
||||
static const std::string keyboard_mp_profile_name = "Keyboard "; // + "(player number)"
|
||||
|
||||
// Make the button value array, which maps a button index to its bit field.
|
||||
#define DEFINE_INPUT(name, value, readable) uint16_t(value##u),
|
||||
static const std::array n64_button_values = {
|
||||
DEFINE_N64_BUTTON_INPUTS()
|
||||
};
|
||||
#undef DEFINE_INPUT
|
||||
|
||||
// Make the input name array.
|
||||
#define DEFINE_INPUT(name, value, readable) readable,
|
||||
static const std::vector<std::string> input_names = {
|
||||
DEFINE_ALL_INPUTS()
|
||||
};
|
||||
#undef DEFINE_INPUT
|
||||
|
||||
// Make the input enum name array.
|
||||
#define DEFINE_INPUT(name, value, readable) #name,
|
||||
static const std::vector<std::string> input_enum_names = {
|
||||
DEFINE_ALL_INPUTS()
|
||||
};
|
||||
#undef DEFINE_INPUT
|
||||
|
||||
size_t recomp::get_num_inputs() {
|
||||
return (size_t)GameInput::COUNT;
|
||||
}
|
||||
|
||||
const std::string& recomp::get_input_name(GameInput input) {
|
||||
return input_names.at(static_cast<size_t>(input));
|
||||
}
|
||||
|
||||
const std::string& recomp::get_input_enum_name(GameInput input) {
|
||||
return input_enum_names.at(static_cast<size_t>(input));
|
||||
}
|
||||
|
||||
recomp::GameInput recomp::get_input_from_enum_name(const std::string_view enum_name) {
|
||||
auto find_it = std::find(input_enum_names.begin(), input_enum_names.end(), enum_name);
|
||||
if (find_it == input_enum_names.end()) {
|
||||
return recomp::GameInput::COUNT;
|
||||
}
|
||||
|
||||
return static_cast<recomp::GameInput>(find_it - input_enum_names.begin());
|
||||
}
|
||||
|
||||
// Due to an RmlUi limitation this can't be const. Ideally it would return a const reference or even just a straight up copy.
|
||||
recomp::InputField& recomp::get_input_binding(int profile_index, GameInput input, size_t binding_index) {
|
||||
input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast<size_t>(input));
|
||||
if (binding_index < cur_input_mapping.size()) {
|
||||
return cur_input_mapping[binding_index];
|
||||
}
|
||||
else {
|
||||
static recomp::InputField dummy_field = {};
|
||||
return dummy_field;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_input_binding(int profile_index, recomp::GameInput input, size_t binding_index, recomp::InputField value) {
|
||||
input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast<size_t>(input));
|
||||
if (binding_index < cur_input_mapping.size()) {
|
||||
cur_input_mapping[binding_index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::clear_input_binding(int profile_index, GameInput input) {
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::reset_input_binding(int profile_index, InputDevice device, GameInput input) {
|
||||
std::vector<recomp::InputField> new_mappings = recomp::get_default_mapping_for_input(
|
||||
device == recomp::InputDevice::Keyboard ?
|
||||
recomp::default_n64_keyboard_mappings :
|
||||
recomp::default_n64_controller_mappings,
|
||||
input
|
||||
);
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
if (binding_index >= new_mappings.size()) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
} else {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, new_mappings[binding_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::reset_profile_bindings(int profile_index, recomp::InputDevice device) {
|
||||
const recomp::DefaultN64Mappings &defaults = (device == recomp::InputDevice::Keyboard)
|
||||
? recomp::default_n64_keyboard_mappings
|
||||
: recomp::default_n64_controller_mappings;
|
||||
|
||||
// multiplayer keyboard profiles just get cleared completely because of overlapping key bindings.
|
||||
bool is_multiplayer_kb = false;
|
||||
if (device == recomp::InputDevice::Keyboard) {
|
||||
for (size_t i = 0; i < recompinput::get_num_players(); i++) {
|
||||
if (recomp::get_mp_keyboard_profile_index(i) == profile_index) {
|
||||
is_multiplayer_kb = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < recomp::get_num_inputs(); i++) {
|
||||
recomp::GameInput input = static_cast<recomp::GameInput>(i);
|
||||
if (is_multiplayer_kb) {
|
||||
recomp::clear_input_binding(profile_index, input);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &new_mappings = recomp::get_default_mapping_for_input(defaults, input);
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
if (binding_index >= new_mappings.size()) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
} else {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, new_mappings[binding_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom) {
|
||||
auto it = input_profile_key_index_map.find(key);
|
||||
if (it != input_profile_key_index_map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int index = (int)(input_profiles.size());
|
||||
InputProfile profile;
|
||||
profile.key = key;
|
||||
profile.name = name;
|
||||
profile.device = device;
|
||||
profile.custom = custom;
|
||||
input_profiles.emplace_back(profile);
|
||||
input_profile_key_index_map.emplace(key, index);
|
||||
|
||||
if (custom) {
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
input_profile_custom_indices[0].emplace_back(index);
|
||||
break;
|
||||
case InputDevice::Keyboard:
|
||||
input_profile_custom_indices[1].emplace_back(index);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int recomp::get_input_profile_by_key(const std::string &key) {
|
||||
auto it = input_profile_key_index_map.find(key);
|
||||
if (it != input_profile_key_index_map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &recomp::get_input_profile_key(int profile_index) {
|
||||
return input_profiles[profile_index].key;
|
||||
}
|
||||
|
||||
const std::string &recomp::get_input_profile_name(int profile_index) {
|
||||
return input_profiles[profile_index].name;
|
||||
}
|
||||
|
||||
recomp::InputDevice recomp::get_input_profile_device(int profile_index) {
|
||||
return input_profiles[profile_index].device;
|
||||
}
|
||||
|
||||
bool recomp::is_input_profile_custom(int profile_index) {
|
||||
return input_profiles[profile_index].custom;
|
||||
}
|
||||
|
||||
int recomp::get_input_profile_count() {
|
||||
return (int)(input_profiles.size());
|
||||
}
|
||||
|
||||
const std::vector<int> recomp::get_indices_for_custom_profiles(InputDevice device) {
|
||||
static std::vector<int> empty;
|
||||
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
return input_profile_custom_indices[0];
|
||||
case InputDevice::Keyboard:
|
||||
return input_profile_custom_indices[1];
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_input_profile_for_player(int player_index, int profile_index, InputDevice device) {
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
players_input_profile_indices[player_index].first = profile_index;
|
||||
break;
|
||||
case InputDevice::Keyboard:
|
||||
players_input_profile_indices[player_index].second = profile_index;
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_input_profile_for_player(int player_index, InputDevice device) {
|
||||
switch (device) {
|
||||
case InputDevice::Controller:
|
||||
return players_input_profile_indices[player_index].first;
|
||||
case InputDevice::Keyboard:
|
||||
return players_input_profile_indices[player_index].second;
|
||||
default:
|
||||
assert(false && "Unknown input device.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::add_controller(ControllerGUID guid, int profile_index) {
|
||||
auto it = controller_hash_index_map.find(guid.hash);
|
||||
if (it != controller_hash_index_map.end()) {
|
||||
controllers[it->second].profile_index = profile_index;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int index = (int)(controllers.size());
|
||||
Controller controller;
|
||||
controller.guid = guid;
|
||||
controller.profile_index = profile_index;
|
||||
controllers.emplace_back(controller);
|
||||
controller_hash_index_map.emplace(guid.hash, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
const recomp::ControllerGUID &recomp::get_controller_guid(int controller_index) {
|
||||
return controllers[controller_index].guid;
|
||||
}
|
||||
|
||||
int recomp::get_controller_profile_index(int controller_index) {
|
||||
return controllers[controller_index].profile_index;
|
||||
}
|
||||
|
||||
int recomp::get_controller_by_guid(ControllerGUID guid) {
|
||||
auto it = controller_hash_index_map.find(guid.hash);
|
||||
if (it != controller_hash_index_map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_controller_count() {
|
||||
return (int)(controllers.size());
|
||||
}
|
||||
|
||||
recomp::ControllerGUID recomp::get_guid_from_sdl_controller(SDL_GameController *game_controller) {
|
||||
if (game_controller == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(game_controller);
|
||||
if (joystick == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Uint16 vendor, product, version, crc16;
|
||||
const char *joystick_serial = SDL_JoystickGetSerial(joystick);
|
||||
SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick);
|
||||
SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16);
|
||||
|
||||
recomp::ControllerGUID guid;
|
||||
guid.serial = joystick_serial != nullptr ? joystick_serial : "";
|
||||
guid.vendor = vendor;
|
||||
guid.product = product;
|
||||
guid.version = version;
|
||||
guid.crc16 = crc16;
|
||||
|
||||
// Compute the hash from the GUID.
|
||||
XXH3_state_t state;
|
||||
XXH3_64bits_reset(&state);
|
||||
XXH3_64bits_update(&state, &guid.vendor, sizeof(guid.vendor));
|
||||
XXH3_64bits_update(&state, &guid.product, sizeof(guid.product));
|
||||
XXH3_64bits_update(&state, &guid.version, sizeof(guid.version));
|
||||
XXH3_64bits_update(&state, &guid.crc16, sizeof(guid.crc16));
|
||||
guid.hash = XXH3_64bits_digest(&state);
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
int recomp::get_controller_profile_index_from_sdl_controller(SDL_GameController *game_controller) {
|
||||
if (game_controller == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ControllerGUID guid = recomp::get_guid_from_sdl_controller(game_controller);
|
||||
if (guid.hash == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int controller_index = recomp::get_controller_by_guid(guid);
|
||||
if (controller_index < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return recomp::get_controller_profile_index(controller_index);
|
||||
}
|
||||
|
||||
std::string get_mp_keyboard_profile_key(int player_index) {
|
||||
return keyboard_mp_profile_key + std::to_string(player_index);
|
||||
}
|
||||
|
||||
std::string get_mp_keyboard_profile_name(int player_index) {
|
||||
return keyboard_mp_profile_name + "(Player " + std::to_string(player_index + 1) + ")";
|
||||
}
|
||||
|
||||
void clear_mapping(int profile_index, recomp::GameInput input) {
|
||||
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
|
||||
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
|
||||
}
|
||||
};
|
||||
|
||||
// Used primarily for multiplayer keyboard input profiles
|
||||
void clear_all_mappings(int profile_index) {
|
||||
clear_mapping(profile_index, recomp::GameInput::A);
|
||||
clear_mapping(profile_index, recomp::GameInput::B);
|
||||
clear_mapping(profile_index, recomp::GameInput::Z);
|
||||
clear_mapping(profile_index, recomp::GameInput::START);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_UP);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_DOWN);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_LEFT);
|
||||
clear_mapping(profile_index, recomp::GameInput::DPAD_RIGHT);
|
||||
clear_mapping(profile_index, recomp::GameInput::L);
|
||||
clear_mapping(profile_index, recomp::GameInput::R);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_UP);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_DOWN);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_LEFT);
|
||||
clear_mapping(profile_index, recomp::GameInput::C_RIGHT);
|
||||
|
||||
clear_mapping(profile_index, recomp::GameInput::X_AXIS_NEG);
|
||||
clear_mapping(profile_index, recomp::GameInput::X_AXIS_POS);
|
||||
clear_mapping(profile_index, recomp::GameInput::Y_AXIS_NEG);
|
||||
clear_mapping(profile_index, recomp::GameInput::Y_AXIS_POS);
|
||||
|
||||
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() {
|
||||
keyboard_sp_profile_index = recomp::add_input_profile(keyboard_sp_profile_key, keyboard_sp_profile_name, recomp::InputDevice::Keyboard, false);
|
||||
controller_sp_profile_index = recomp::add_input_profile(controller_sp_profile_key, controller_sp_profile_name, recomp::InputDevice::Controller, false);
|
||||
|
||||
// Set Player 1 to the SP profiles by default.
|
||||
recomp::set_input_profile_for_player(0, keyboard_sp_profile_index, recomp::InputDevice::Keyboard);
|
||||
recomp::set_input_profile_for_player(0, controller_sp_profile_index, recomp::InputDevice::Controller);
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
int profile_index = recomp::add_input_profile(
|
||||
get_mp_keyboard_profile_key(i),
|
||||
get_mp_keyboard_profile_name(i),
|
||||
recomp::InputDevice::Keyboard,
|
||||
false
|
||||
);
|
||||
clear_all_mappings(profile_index);
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_sp_controller_profile_index() {
|
||||
return controller_sp_profile_index;
|
||||
}
|
||||
|
||||
int recomp::get_sp_keyboard_profile_index() {
|
||||
return keyboard_sp_profile_index;
|
||||
}
|
||||
|
||||
int recomp::get_mp_keyboard_profile_index(int player_index) {
|
||||
return recomp::get_input_profile_by_key(get_mp_keyboard_profile_key(player_index));
|
||||
}
|
||||
|
||||
std::string recomp::get_string_from_controller_guid(ControllerGUID guid) {
|
||||
return "SERIAL_" + guid.serial + "_VID_" + std::to_string(guid.vendor) + "_PID_" + std::to_string(guid.product) +"_VERSION_" + std::to_string(guid.version) +"_CRC16_" + std::to_string(guid.crc16);
|
||||
}
|
||||
|
||||
bool recomp::get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out) {
|
||||
uint16_t cur_buttons = 0;
|
||||
float cur_x = 0.0f;
|
||||
float cur_y = 0.0f;
|
||||
if (!recomp::game_input_disabled()) {
|
||||
auto check_buttons = [&](int profile_index) {
|
||||
if (profile_index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const input_mapping_array &mappings = input_profiles[profile_index].mappings;
|
||||
for (size_t i = 0; i < n64_button_values.size(); i++) {
|
||||
cur_buttons |= recomp::get_input_digital(player_index, mappings[(size_t)(GameInput::N64_BUTTON_START) + i]) ? n64_button_values[i] : 0;
|
||||
}
|
||||
};
|
||||
|
||||
auto check_joystick = [&](int profile_index) {
|
||||
if (profile_index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const input_mapping_array &mappings = input_profiles[profile_index].mappings;
|
||||
cur_x += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_NEG]);
|
||||
cur_y += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_NEG]);
|
||||
};
|
||||
|
||||
check_buttons(players_input_profile_indices[player_index].first);
|
||||
check_buttons(players_input_profile_indices[player_index].second);
|
||||
|
||||
check_joystick(players_input_profile_indices[player_index].first);
|
||||
recomp::apply_joystick_deadzone(cur_x, cur_y, &cur_x, &cur_y);
|
||||
check_joystick(players_input_profile_indices[player_index].second);
|
||||
}
|
||||
|
||||
*buttons_out = cur_buttons;
|
||||
*x_out = std::clamp(cur_x, -1.0f, 1.0f);
|
||||
*y_out = std::clamp(cur_y, -1.0f, 1.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
-1175
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "librecomp/addresses.hpp"
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
#include "recomp_ui.h"
|
||||
#include "recompui/recompui.h"
|
||||
#include "recomp_data.h"
|
||||
#include "../patches/actor_funcs.h"
|
||||
|
||||
|
||||
+12
-9
@@ -4,8 +4,9 @@
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/addresses.hpp"
|
||||
#include "banjo_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recompinput/recompinput.h"
|
||||
#include "recompui/recompui.h"
|
||||
#include "recompui/renderer.h"
|
||||
#include "banjo_render.h"
|
||||
#include "banjo_sound.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
@@ -16,7 +17,7 @@
|
||||
#include "ultramodern/config.hpp"
|
||||
|
||||
extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
recomp::poll_inputs();
|
||||
recompinput::poll_inputs();
|
||||
}
|
||||
|
||||
extern "C" void recomp_puts(uint8_t* rdram, recomp_context* ctx) {
|
||||
@@ -49,14 +50,15 @@ extern "C" void recomp_get_gyro_deltas(uint8_t* rdram, recomp_context* ctx) {
|
||||
float* x_out = _arg<0, float*>(rdram, ctx);
|
||||
float* y_out = _arg<1, float*>(rdram, ctx);
|
||||
|
||||
recomp::get_gyro_deltas(x_out, y_out);
|
||||
// TODO: use controller number
|
||||
recompinput::get_gyro_deltas(0, x_out, y_out);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_mouse_deltas(uint8_t* rdram, recomp_context* ctx) {
|
||||
float* x_out = _arg<0, float*>(rdram, ctx);
|
||||
float* y_out = _arg<1, float*>(rdram, ctx);
|
||||
|
||||
recomp::get_mouse_deltas(x_out, y_out);
|
||||
recompinput::get_mouse_deltas(x_out, y_out);
|
||||
}
|
||||
|
||||
extern "C" void recomp_powf(uint8_t* rdram, recomp_context* ctx) {
|
||||
@@ -117,7 +119,7 @@ extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void recomp_high_precision_fb_enabled(uint8_t * rdram, recomp_context * ctx) {
|
||||
_return(ctx, static_cast<s32>(banjo::renderer::RT64HighPrecisionFBEnabled()));
|
||||
_return(ctx, static_cast<s32>(recompui::renderer::RT64HighPrecisionFBEnabled()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_resolution_scale(uint8_t* rdram, recomp_context* ctx) {
|
||||
@@ -145,7 +147,7 @@ extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context*
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return<s32>(ctx, banjo::get_analog_cam_mode() == banjo::AnalogCamMode::On);
|
||||
_return<s32>(ctx, banjo::get_analog_cam_mode());
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
@@ -157,7 +159,8 @@ extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
|
||||
float x, y;
|
||||
|
||||
recomp::get_right_analog(0, &x, &y);
|
||||
// TODO: use controller number
|
||||
recompinput::get_right_analog(0, &x, &y);
|
||||
|
||||
float magnitude = sqrtf(x * x + y * y);
|
||||
|
||||
@@ -177,7 +180,7 @@ extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
extern "C" void recomp_set_right_analog_suppressed(uint8_t* rdram, recomp_context* ctx) {
|
||||
s32 suppressed = _arg<0, s32>(rdram, ctx);
|
||||
|
||||
recomp::set_right_analog_suppressed(suppressed);
|
||||
recompinput::set_right_analog_suppressed(suppressed);
|
||||
}
|
||||
|
||||
// Function with typo in decomp
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "slot_map.h"
|
||||
#include "recomp_data.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recompui/recompui.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/addresses.hpp"
|
||||
|
||||
+28
-20
@@ -20,8 +20,14 @@
|
||||
#include "SDL2/SDL_syswm.h"
|
||||
#endif
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recompui/recompui.h"
|
||||
#include "recompui/program_config.h"
|
||||
#include "recompui/renderer.h"
|
||||
#include "recompui/config.h"
|
||||
#include "util/file.h"
|
||||
#include "recompinput/input_events.h"
|
||||
#include "recompinput/recompinput.h"
|
||||
#include "recompinput/profiles.h"
|
||||
#include "banjo_config.h"
|
||||
#include "banjo_sound.h"
|
||||
#include "banjo_render.h"
|
||||
@@ -166,7 +172,7 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::
|
||||
}
|
||||
|
||||
void update_gfx(void*) {
|
||||
recomp::handle_events();
|
||||
recompinput::handle_events();
|
||||
}
|
||||
|
||||
static SDL_AudioCVT audio_convert;
|
||||
@@ -208,7 +214,7 @@ void queue_samples(int16_t* audio_data, size_t sample_count) {
|
||||
|
||||
// Convert the audio from 16-bit values to floats and swap the audio channels into the
|
||||
// swap buffer to correct for the address xor caused by endianness handling.
|
||||
float cur_main_volume = banjo::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
float cur_main_volume = static_cast<float>(recompui::config::sound::get_main_volume()) / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
for (size_t i = 0; i < sample_count; i += input_channels) {
|
||||
swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
@@ -511,15 +517,15 @@ void release_preload(PreloadContext& context) {
|
||||
#endif
|
||||
|
||||
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||
banjo::renderer::enable_texture_pack(context, mod);
|
||||
recompui::renderer::enable_texture_pack(context, mod);
|
||||
}
|
||||
|
||||
void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) {
|
||||
banjo::renderer::disable_texture_pack(mod);
|
||||
recompui::renderer::disable_texture_pack(mod);
|
||||
}
|
||||
|
||||
void reorder_texture_pack(recomp::mods::ModContext&) {
|
||||
banjo::renderer::trigger_texture_pack_update();
|
||||
recompui::renderer::trigger_texture_pack_update();
|
||||
}
|
||||
|
||||
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||
@@ -579,12 +585,14 @@ int main(int argc, char** argv) {
|
||||
reset_audio(48000);
|
||||
|
||||
// Source controller mappings file
|
||||
std::u8string controller_db_path = (banjo::get_program_path() / "recompcontrollerdb.txt").u8string();
|
||||
std::u8string controller_db_path = (recompui::file::get_program_path() / "recompcontrollerdb.txt").u8string();
|
||||
if (SDL_GameControllerAddMappingsFromFile(reinterpret_cast<const char *>(controller_db_path.c_str())) < 0) {
|
||||
fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
recomp::register_config_path(banjo::get_app_folder_path());
|
||||
recompui::programconfig::set_program_name(banjo::program_name);
|
||||
recompui::programconfig::set_program_id(banjo::program_id);
|
||||
recomp::register_config_path(recompui::file::get_app_folder_path());
|
||||
|
||||
// Register supported games and patches
|
||||
for (const auto& game : supported_games) {
|
||||
@@ -608,14 +616,17 @@ int main(int argc, char** argv) {
|
||||
banjo::register_bk_overlays();
|
||||
banjo::register_bk_patches();
|
||||
recomputil::init_extended_actor_data();
|
||||
banjo::load_config();
|
||||
|
||||
recompinput::players::set_max_number_of_players(4);
|
||||
|
||||
banjo::init_config();
|
||||
|
||||
recomp::rsp::callbacks_t rsp_callbacks{
|
||||
.get_rsp_microcode = get_rsp_microcode,
|
||||
};
|
||||
|
||||
ultramodern::renderer::callbacks_t renderer_callbacks{
|
||||
.create_render_context = banjo::renderer::create_render_context,
|
||||
.create_render_context = recompui::renderer::create_render_context,
|
||||
};
|
||||
|
||||
ultramodern::gfx_callbacks_t gfx_callbacks{
|
||||
@@ -631,15 +642,15 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
ultramodern::input::callbacks_t input_callbacks{
|
||||
.poll_input = recomp::poll_inputs,
|
||||
.get_input = recomp::get_n64_input,
|
||||
.set_rumble = recomp::set_rumble,
|
||||
.get_connected_device_info = recomp::get_connected_device_info,
|
||||
.poll_input = recompinput::poll_inputs,
|
||||
.get_input = recompinput::profiles::get_n64_input,
|
||||
.set_rumble = recompinput::set_rumble,
|
||||
.get_connected_device_info = recompinput::get_connected_device_info,
|
||||
};
|
||||
|
||||
ultramodern::events::callbacks_t thread_callbacks{
|
||||
.vi_callback = recomp::update_rumble,
|
||||
.gfx_init_callback = recompui::update_supported_options,
|
||||
.vi_callback = recompinput::update_rumble,
|
||||
.gfx_init_callback = nullptr,
|
||||
};
|
||||
|
||||
ultramodern::error_handling::callbacks_t error_handling_callbacks{
|
||||
@@ -663,9 +674,6 @@ int main(int argc, char** argv) {
|
||||
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
|
||||
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
|
||||
|
||||
// TODO: Where is it best to place this?
|
||||
recomp::set_single_controller_mode(false);
|
||||
|
||||
recomp::start(
|
||||
project_version,
|
||||
{},
|
||||
|
||||
@@ -1,548 +0,0 @@
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <variant>
|
||||
#include <algorithm>
|
||||
|
||||
#define HLSL_CPU
|
||||
#include "hle/rt64_application.h"
|
||||
#include "rt64_render_hooks.h"
|
||||
#include "overloaded.h"
|
||||
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
|
||||
#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;
|
||||
static bool high_precision_fb_enabled = false;
|
||||
|
||||
static uint8_t DMEM[0x1000];
|
||||
static uint8_t IMEM[0x1000];
|
||||
|
||||
struct TexturePackEnableAction {
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
struct TexturePackDisableAction {
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
struct TexturePackSecondaryEnableAction {
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
struct TexturePackSecondaryDisableAction {
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
struct TexturePackUpdateAction {
|
||||
};
|
||||
|
||||
using TexturePackAction = std::variant<TexturePackEnableAction, TexturePackDisableAction, TexturePackSecondaryEnableAction, TexturePackSecondaryDisableAction, TexturePackUpdateAction>;
|
||||
|
||||
static moodycamel::ConcurrentQueue<TexturePackAction> texture_pack_action_queue;
|
||||
|
||||
unsigned int MI_INTR_REG = 0;
|
||||
|
||||
unsigned int DPC_START_REG = 0;
|
||||
unsigned int DPC_END_REG = 0;
|
||||
unsigned int DPC_CURRENT_REG = 0;
|
||||
unsigned int DPC_STATUS_REG = 0;
|
||||
unsigned int DPC_CLOCK_REG = 0;
|
||||
unsigned int DPC_BUFBUSY_REG = 0;
|
||||
unsigned int DPC_PIPEBUSY_REG = 0;
|
||||
unsigned int DPC_TMEM_REG = 0;
|
||||
|
||||
unsigned int VI_STATUS_REG = 0;
|
||||
unsigned int VI_ORIGIN_REG = 0;
|
||||
unsigned int VI_WIDTH_REG = 0;
|
||||
unsigned int VI_INTR_REG = 0;
|
||||
unsigned int VI_V_CURRENT_LINE_REG = 0;
|
||||
unsigned int VI_TIMING_REG = 0;
|
||||
unsigned int VI_V_SYNC_REG = 0;
|
||||
unsigned int VI_H_SYNC_REG = 0;
|
||||
unsigned int VI_LEAP_REG = 0;
|
||||
unsigned int VI_H_START_REG = 0;
|
||||
unsigned int VI_V_START_REG = 0;
|
||||
unsigned int VI_V_BURST_REG = 0;
|
||||
unsigned int VI_X_SCALE_REG = 0;
|
||||
unsigned int VI_Y_SCALE_REG = 0;
|
||||
|
||||
void dummy_check_interrupts() {}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampleCounts bits) {
|
||||
if (bits & RT64::RenderSampleCount::Bits::COUNT_2) {
|
||||
if (bits & RT64::RenderSampleCount::Bits::COUNT_4) {
|
||||
if (bits & RT64::RenderSampleCount::Bits::COUNT_8) {
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
}
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
}
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
};
|
||||
return RT64::UserConfiguration::Antialiasing::None;
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::AspectRatio to_rt64(ultramodern::renderer::AspectRatio option) {
|
||||
switch (option) {
|
||||
case ultramodern::renderer::AspectRatio::Original:
|
||||
return RT64::UserConfiguration::AspectRatio::Original;
|
||||
case ultramodern::renderer::AspectRatio::Expand:
|
||||
return RT64::UserConfiguration::AspectRatio::Expand;
|
||||
case ultramodern::renderer::AspectRatio::Manual:
|
||||
return RT64::UserConfiguration::AspectRatio::Manual;
|
||||
case ultramodern::renderer::AspectRatio::OptionCount:
|
||||
return RT64::UserConfiguration::AspectRatio::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing to_rt64(ultramodern::renderer::Antialiasing option) {
|
||||
switch (option) {
|
||||
case ultramodern::renderer::Antialiasing::None:
|
||||
return RT64::UserConfiguration::Antialiasing::None;
|
||||
case ultramodern::renderer::Antialiasing::MSAA2X:
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
case ultramodern::renderer::Antialiasing::MSAA4X:
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
case ultramodern::renderer::Antialiasing::MSAA8X:
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
case ultramodern::renderer::Antialiasing::OptionCount:
|
||||
return RT64::UserConfiguration::Antialiasing::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::RefreshRate to_rt64(ultramodern::renderer::RefreshRate option) {
|
||||
switch (option) {
|
||||
case ultramodern::renderer::RefreshRate::Original:
|
||||
return RT64::UserConfiguration::RefreshRate::Original;
|
||||
case ultramodern::renderer::RefreshRate::Display:
|
||||
return RT64::UserConfiguration::RefreshRate::Display;
|
||||
case ultramodern::renderer::RefreshRate::Manual:
|
||||
return RT64::UserConfiguration::RefreshRate::Manual;
|
||||
case ultramodern::renderer::RefreshRate::OptionCount:
|
||||
return RT64::UserConfiguration::RefreshRate::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::renderer::HighPrecisionFramebuffer option) {
|
||||
switch (option) {
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::Off:
|
||||
return RT64::UserConfiguration::InternalColorFormat::Standard;
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::On:
|
||||
return RT64::UserConfiguration::InternalColorFormat::High;
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::Auto:
|
||||
return RT64::UserConfiguration::InternalColorFormat::Automatic;
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::OptionCount:
|
||||
return RT64::UserConfiguration::InternalColorFormat::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
void set_application_user_config(RT64::Application* application, const ultramodern::renderer::GraphicsConfig& config) {
|
||||
switch (config.res_option) {
|
||||
default:
|
||||
case ultramodern::renderer::Resolution::Auto:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale;
|
||||
application->userConfig.downsampleMultiplier = 1;
|
||||
break;
|
||||
case ultramodern::renderer::Resolution::Original:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual;
|
||||
application->userConfig.resolutionMultiplier = std::max(config.ds_option, 1);
|
||||
application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1);
|
||||
break;
|
||||
case ultramodern::renderer::Resolution::Original2x:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual;
|
||||
application->userConfig.resolutionMultiplier = 2.0 * std::max(config.ds_option, 1);
|
||||
application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (config.hr_option) {
|
||||
default:
|
||||
case ultramodern::renderer::HUDRatioMode::Original:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Original;
|
||||
break;
|
||||
case ultramodern::renderer::HUDRatioMode::Clamp16x9:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Manual;
|
||||
application->userConfig.extAspectTarget = 16.0/9.0;
|
||||
break;
|
||||
case ultramodern::renderer::HUDRatioMode::Full:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Expand;
|
||||
break;
|
||||
}
|
||||
|
||||
application->userConfig.aspectRatio = to_rt64(config.ar_option);
|
||||
application->userConfig.antialiasing = to_rt64(config.msaa_option);
|
||||
application->userConfig.refreshRate = to_rt64(config.rr_option);
|
||||
application->userConfig.refreshRateTarget = config.rr_manual_value;
|
||||
application->userConfig.internalColorFormat = to_rt64(config.hpfb_option);
|
||||
application->userConfig.displayBuffering = RT64::UserConfiguration::DisplayBuffering::Triple;
|
||||
}
|
||||
|
||||
ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
|
||||
switch (rt64_result) {
|
||||
case RT64::Application::SetupResult::Success:
|
||||
return ultramodern::renderer::SetupResult::Success;
|
||||
case RT64::Application::SetupResult::DynamicLibrariesNotFound:
|
||||
return ultramodern::renderer::SetupResult::DynamicLibrariesNotFound;
|
||||
case RT64::Application::SetupResult::InvalidGraphicsAPI:
|
||||
return ultramodern::renderer::SetupResult::InvalidGraphicsAPI;
|
||||
case RT64::Application::SetupResult::GraphicsAPINotFound:
|
||||
return ultramodern::renderer::SetupResult::GraphicsAPINotFound;
|
||||
case RT64::Application::SetupResult::GraphicsDeviceNotFound:
|
||||
return ultramodern::renderer::SetupResult::GraphicsDeviceNotFound;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unhandled `RT64::Application::SetupResult` ?\n");
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ultramodern::renderer::GraphicsApi map_graphics_api(RT64::UserConfiguration::GraphicsAPI api) {
|
||||
switch (api) {
|
||||
case RT64::UserConfiguration::GraphicsAPI::D3D12:
|
||||
return ultramodern::renderer::GraphicsApi::D3D12;
|
||||
case RT64::UserConfiguration::GraphicsAPI::Vulkan:
|
||||
return ultramodern::renderer::GraphicsApi::Vulkan;
|
||||
case RT64::UserConfiguration::GraphicsAPI::Metal:
|
||||
return ultramodern::renderer::GraphicsApi::Metal;
|
||||
case RT64::UserConfiguration::GraphicsAPI::Automatic:
|
||||
return ultramodern::renderer::GraphicsApi::Auto;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unhandled `RT64::UserConfiguration::GraphicsAPI` ?\n");
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) {
|
||||
static unsigned char dummy_rom_header[0x40];
|
||||
recompui::set_render_hooks();
|
||||
|
||||
// Set up the RT64 application core fields.
|
||||
RT64::Application::Core appCore{};
|
||||
#if defined(_WIN32)
|
||||
appCore.window = window_handle.window;
|
||||
#elif defined(__linux__) || defined(__ANDROID__)
|
||||
appCore.window = window_handle;
|
||||
#elif defined(__APPLE__)
|
||||
appCore.window.window = window_handle.window;
|
||||
appCore.window.view = window_handle.view;
|
||||
#endif
|
||||
|
||||
appCore.checkInterrupts = dummy_check_interrupts;
|
||||
|
||||
appCore.HEADER = dummy_rom_header;
|
||||
appCore.RDRAM = rdram;
|
||||
appCore.DMEM = DMEM;
|
||||
appCore.IMEM = IMEM;
|
||||
|
||||
appCore.MI_INTR_REG = &MI_INTR_REG;
|
||||
|
||||
appCore.DPC_START_REG = &DPC_START_REG;
|
||||
appCore.DPC_END_REG = &DPC_END_REG;
|
||||
appCore.DPC_CURRENT_REG = &DPC_CURRENT_REG;
|
||||
appCore.DPC_STATUS_REG = &DPC_STATUS_REG;
|
||||
appCore.DPC_CLOCK_REG = &DPC_CLOCK_REG;
|
||||
appCore.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG;
|
||||
appCore.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG;
|
||||
appCore.DPC_TMEM_REG = &DPC_TMEM_REG;
|
||||
|
||||
appCore.VI_STATUS_REG = &VI_STATUS_REG;
|
||||
appCore.VI_ORIGIN_REG = &VI_ORIGIN_REG;
|
||||
appCore.VI_WIDTH_REG = &VI_WIDTH_REG;
|
||||
appCore.VI_INTR_REG = &VI_INTR_REG;
|
||||
appCore.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG;
|
||||
appCore.VI_TIMING_REG = &VI_TIMING_REG;
|
||||
appCore.VI_V_SYNC_REG = &VI_V_SYNC_REG;
|
||||
appCore.VI_H_SYNC_REG = &VI_H_SYNC_REG;
|
||||
appCore.VI_LEAP_REG = &VI_LEAP_REG;
|
||||
appCore.VI_H_START_REG = &VI_H_START_REG;
|
||||
appCore.VI_V_START_REG = &VI_V_START_REG;
|
||||
appCore.VI_V_BURST_REG = &VI_V_BURST_REG;
|
||||
appCore.VI_X_SCALE_REG = &VI_X_SCALE_REG;
|
||||
appCore.VI_Y_SCALE_REG = &VI_Y_SCALE_REG;
|
||||
|
||||
// Set up the RT64 application configuration fields.
|
||||
RT64::ApplicationConfiguration appConfig;
|
||||
appConfig.useConfigurationFile = false;
|
||||
|
||||
// Create the RT64 application.
|
||||
app = std::make_unique<RT64::Application>(appCore, appConfig);
|
||||
|
||||
// Set initial user config settings based on the current settings.
|
||||
auto& cur_config = ultramodern::renderer::get_graphics_config();
|
||||
set_application_user_config(app.get(), cur_config);
|
||||
app->userConfig.developerMode = debug;
|
||||
// Force gbi depth branches to prevent LODs from kicking in.
|
||||
app->enhancementConfig.f3dex.forceBranch = true;
|
||||
// Scale LODs based on the output resolution.
|
||||
app->enhancementConfig.textureLOD.scale = true;
|
||||
// Pick an API if the user has set an override.
|
||||
switch (cur_config.api_option) {
|
||||
case ultramodern::renderer::GraphicsApi::D3D12:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::D3D12;
|
||||
break;
|
||||
case ultramodern::renderer::GraphicsApi::Vulkan:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan;
|
||||
break;
|
||||
case ultramodern::renderer::GraphicsApi::Metal:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Metal;
|
||||
break;
|
||||
case ultramodern::renderer::GraphicsApi::Auto:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Automatic;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up the RT64 application.
|
||||
uint32_t thread_id = 0;
|
||||
#ifdef _WIN32
|
||||
thread_id = window_handle.thread_id;
|
||||
#endif
|
||||
setup_result = map_setup_result(app->setup(thread_id));
|
||||
// Get the API that RT64 chose.
|
||||
chosen_api = map_graphics_api(app->chosenGraphicsAPI);
|
||||
if (setup_result != ultramodern::renderer::SetupResult::Success) {
|
||||
app = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the application's fullscreen state.
|
||||
app->setFullScreen(cur_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen);
|
||||
|
||||
// Check if the selected device actually supports MSAA sample positions and MSAA for for the formats that will be used
|
||||
// and downgrade the configuration accordingly.
|
||||
if (app->device->getCapabilities().sampleLocations) {
|
||||
RT64::RenderSampleCounts color_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::R8G8B8A8_UNORM);
|
||||
RT64::RenderSampleCounts depth_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::D32_FLOAT);
|
||||
RT64::RenderSampleCounts common_sample_counts = color_sample_counts & depth_sample_counts;
|
||||
device_max_msaa = compute_max_supported_aa(common_sample_counts);
|
||||
sample_positions_supported = true;
|
||||
}
|
||||
else {
|
||||
device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||
sample_positions_supported = false;
|
||||
}
|
||||
|
||||
recompui::update_msaa_supported(sample_positions_supported);
|
||||
|
||||
high_precision_fb_enabled = app->shaderLibrary->usesHDR;
|
||||
}
|
||||
|
||||
banjo::renderer::RT64Context::~RT64Context() = default;
|
||||
|
||||
void banjo::renderer::RT64Context::send_dl(const OSTask* task) {
|
||||
check_texture_pack_actions();
|
||||
app->state->rsp->reset();
|
||||
app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true);
|
||||
app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true);
|
||||
}
|
||||
|
||||
void banjo::renderer::RT64Context::update_screen(uint32_t vi_origin) {
|
||||
VI_ORIGIN_REG = vi_origin;
|
||||
|
||||
app->updateScreen();
|
||||
}
|
||||
|
||||
void banjo::renderer::RT64Context::shutdown() {
|
||||
if (app != nullptr) {
|
||||
app->end();
|
||||
}
|
||||
}
|
||||
|
||||
bool banjo::renderer::RT64Context::update_config(const ultramodern::renderer::GraphicsConfig& old_config, const ultramodern::renderer::GraphicsConfig& new_config) {
|
||||
if (old_config == new_config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_config.wm_option != old_config.wm_option) {
|
||||
app->setFullScreen(new_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen);
|
||||
}
|
||||
|
||||
set_application_user_config(app.get(), new_config);
|
||||
|
||||
app->updateUserConfig(true);
|
||||
|
||||
if (new_config.msaa_option != old_config.msaa_option) {
|
||||
app->updateMultisampling();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void banjo::renderer::RT64Context::enable_instant_present() {
|
||||
// Enable the present early presentation mode for minimal latency.
|
||||
app->enhancementConfig.presentation.mode = RT64::EnhancementConfiguration::Presentation::Mode::PresentEarly;
|
||||
|
||||
app->updateEnhancementConfig();
|
||||
}
|
||||
|
||||
uint32_t banjo::renderer::RT64Context::get_display_framerate() const {
|
||||
return app->presentQueue->ext.sharedResources->swapChainRate;
|
||||
}
|
||||
|
||||
float banjo::renderer::RT64Context::get_resolution_scale() const {
|
||||
constexpr int ReferenceHeight = 240;
|
||||
switch (app->userConfig.resolution) {
|
||||
case RT64::UserConfiguration::Resolution::WindowIntegerScale:
|
||||
if (app->sharedQueueResources->swapChainHeight > 0) {
|
||||
return std::max(float((app->sharedQueueResources->swapChainHeight + ReferenceHeight - 1) / ReferenceHeight), 1.0f);
|
||||
}
|
||||
else {
|
||||
return 1.0f;
|
||||
}
|
||||
case RT64::UserConfiguration::Resolution::Manual:
|
||||
return float(app->userConfig.resolutionMultiplier);
|
||||
case RT64::UserConfiguration::Resolution::Original:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void banjo::renderer::RT64Context::check_texture_pack_actions() {
|
||||
bool packs_changed = false;
|
||||
TexturePackAction cur_action;
|
||||
while (texture_pack_action_queue.try_dequeue(cur_action)) {
|
||||
std::visit(overloaded{
|
||||
[&](TexturePackDisableAction &to_disable) {
|
||||
enabled_texture_packs.erase(to_disable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackEnableAction &to_enable) {
|
||||
enabled_texture_packs.insert(to_enable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackSecondaryDisableAction &to_override_disable) {
|
||||
secondary_disabled_texture_packs.insert(to_override_disable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackSecondaryEnableAction &to_override_enable) {
|
||||
secondary_disabled_texture_packs.erase(to_override_enable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackUpdateAction &) {
|
||||
packs_changed = true;
|
||||
}
|
||||
}, cur_action);
|
||||
}
|
||||
|
||||
// If any packs were disabled, unload all packs and load all the active ones.
|
||||
if (packs_changed) {
|
||||
// Sort the enabled texture packs in reverse order so that earlier ones override later ones.
|
||||
std::vector<std::string> sorted_texture_packs{};
|
||||
sorted_texture_packs.reserve(enabled_texture_packs.size());
|
||||
for (const std::string& mod : enabled_texture_packs) {
|
||||
if (!secondary_disabled_texture_packs.contains(mod)) {
|
||||
sorted_texture_packs.emplace_back(mod);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(sorted_texture_packs.begin(), sorted_texture_packs.end(),
|
||||
[](const std::string& lhs, const std::string& rhs) {
|
||||
return recomp::mods::get_mod_order_index(lhs) > recomp::mods::get_mod_order_index(rhs);
|
||||
}
|
||||
);
|
||||
|
||||
// Build the path list from the sorted mod list.
|
||||
std::vector<RT64::ReplacementDirectory> replacement_directories;
|
||||
replacement_directories.reserve(enabled_texture_packs.size());
|
||||
for (const std::string &mod_id : sorted_texture_packs) {
|
||||
replacement_directories.emplace_back(RT64::ReplacementDirectory(recomp::mods::get_mod_filename(mod_id)));
|
||||
}
|
||||
|
||||
if (!replacement_directories.empty()) {
|
||||
app->textureCache->loadReplacementDirectories(replacement_directories);
|
||||
}
|
||||
else {
|
||||
app->textureCache->clearReplacementDirectories();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing banjo::renderer::RT64MaxMSAA() {
|
||||
return device_max_msaa;
|
||||
}
|
||||
|
||||
std::unique_ptr<ultramodern::renderer::RendererContext> banjo::renderer::create_render_context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode) {
|
||||
return std::make_unique<banjo::renderer::RT64Context>(rdram, window_handle, developer_mode);
|
||||
}
|
||||
|
||||
bool banjo::renderer::RT64SamplePositionsSupported() {
|
||||
return sample_positions_supported;
|
||||
}
|
||||
|
||||
bool banjo::renderer::RT64HighPrecisionFBEnabled() {
|
||||
return high_precision_fb_enabled;
|
||||
}
|
||||
|
||||
void banjo::renderer::trigger_texture_pack_update() {
|
||||
texture_pack_action_queue.enqueue(TexturePackUpdateAction{});
|
||||
}
|
||||
|
||||
void banjo::renderer::enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_id});
|
||||
|
||||
// Check for the texture pack enabled config option.
|
||||
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::config::ConfigOption& config_option = config_schema.options[find_it->second];
|
||||
|
||||
if (is_texture_pack_enable_config_option(config_option, false)) {
|
||||
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;
|
||||
}
|
||||
else {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
banjo::renderer::secondary_enable_texture_pack(mod.manifest.mod_id);
|
||||
}
|
||||
else {
|
||||
banjo::renderer::secondary_disable_texture_pack(mod.manifest.mod_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void banjo::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) {
|
||||
texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_id});
|
||||
}
|
||||
|
||||
void banjo::renderer::secondary_enable_texture_pack(const std::string& mod_id) {
|
||||
texture_pack_action_queue.enqueue(TexturePackSecondaryEnableAction{mod_id});
|
||||
}
|
||||
|
||||
void banjo::renderer::secondary_disable_texture_pack(const std::string& mod_id) {
|
||||
texture_pack_action_queue.enqueue(TexturePackSecondaryDisableAction{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::config::ConfigOption& option, bool show_errors) {
|
||||
if (option.id == banjo::renderer::special_option_texture_pack_enabled) {
|
||||
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::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());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
#include "banjo_support.h"
|
||||
#include <SDL.h>
|
||||
#include "nfd.h"
|
||||
#include "RmlUi/Core.h"
|
||||
|
||||
namespace banjo {
|
||||
// MARK: - Internal Helpers
|
||||
void perform_file_dialog_operation(const std::function<void(bool, const std::filesystem::path&)>& callback) {
|
||||
nfdnchar_t* native_path = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
|
||||
|
||||
bool success = (result == NFD_OKAY);
|
||||
std::filesystem::path path;
|
||||
|
||||
if (success) {
|
||||
path = std::filesystem::path{native_path};
|
||||
NFD_FreePathN(native_path);
|
||||
}
|
||||
|
||||
callback(success, path);
|
||||
}
|
||||
|
||||
void perform_file_dialog_operation_multiple(const std::function<void(bool, const std::list<std::filesystem::path>&)>& callback) {
|
||||
const nfdpathset_t* native_paths = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialogMultipleN(&native_paths, nullptr, 0, nullptr);
|
||||
|
||||
bool success = (result == NFD_OKAY);
|
||||
std::list<std::filesystem::path> paths;
|
||||
nfdpathsetsize_t count = 0;
|
||||
|
||||
if (success) {
|
||||
NFD_PathSet_GetCount(native_paths, &count);
|
||||
for (nfdpathsetsize_t i = 0; i < count; i++) {
|
||||
nfdnchar_t* cur_path = nullptr;
|
||||
nfdresult_t cur_result = NFD_PathSet_GetPathN(native_paths, i, &cur_path);
|
||||
if (cur_result == NFD_OKAY) {
|
||||
paths.emplace_back(std::filesystem::path{cur_path});
|
||||
}
|
||||
}
|
||||
NFD_PathSet_Free(native_paths);
|
||||
}
|
||||
|
||||
callback(success, paths);
|
||||
}
|
||||
|
||||
// MARK: - Public API
|
||||
|
||||
std::filesystem::path get_program_path() {
|
||||
#if defined(__APPLE__)
|
||||
return get_bundle_resource_directory();
|
||||
#elif defined(__linux__) && defined(RECOMP_FLATPAK)
|
||||
return "/app/bin";
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path get_asset_path(const char* asset) {
|
||||
return get_program_path() / "assets" / asset;
|
||||
}
|
||||
|
||||
void open_file_dialog(std::function<void(bool success, const std::filesystem::path& path)> callback) {
|
||||
#ifdef __APPLE__
|
||||
dispatch_on_ui_thread([callback]() {
|
||||
perform_file_dialog_operation(callback);
|
||||
});
|
||||
#else
|
||||
perform_file_dialog_operation(callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
void open_file_dialog_multiple(std::function<void(bool success, const std::list<std::filesystem::path>& paths)> callback) {
|
||||
#ifdef __APPLE__
|
||||
dispatch_on_ui_thread([callback]() {
|
||||
perform_file_dialog_operation_multiple(callback);
|
||||
});
|
||||
#else
|
||||
perform_file_dialog_operation_multiple(callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
void show_error_message_box(const char *title, const char *message) {
|
||||
#ifdef __APPLE__
|
||||
std::string title_copy(title);
|
||||
std::string message_copy(message);
|
||||
|
||||
dispatch_on_ui_thread([title_copy, message_copy] {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title_copy.c_str(), message_copy.c_str(), nullptr);
|
||||
});
|
||||
#else
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, nullptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "elements/ui_theme.h"
|
||||
#include "theme.h"
|
||||
|
||||
void recomptheme::set_custom_theme() {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
#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));
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,293 +0,0 @@
|
||||
#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
|
||||
@@ -1,131 +0,0 @@
|
||||
#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,513 +0,0 @@
|
||||
#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"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ConfigPageControls *controls_page = nullptr;
|
||||
|
||||
const std::string_view active_state_style_name = "cont_opt_active";
|
||||
|
||||
GameInputRow::GameInputRow(
|
||||
Element *parent,
|
||||
GameInputContext *input_ctx,
|
||||
std::function<void()> on_hover_callback,
|
||||
on_bind_click_callback on_bind_click,
|
||||
on_clear_or_reset_callback on_clear_or_reset
|
||||
) : Element(parent, Events(EventType::Hover), "div", false) {
|
||||
this->input_id = input_ctx->input_id;
|
||||
this->on_hover_callback = on_hover_callback;
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::SpaceBetween);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height_auto();
|
||||
|
||||
set_padding_top(4.0f);
|
||||
set_padding_right(16.0f);
|
||||
set_padding_bottom(4.0f);
|
||||
set_padding_left(20.0f);
|
||||
set_border_radius(theme::border::radius_sm);
|
||||
set_background_color(theme::color::Transparent);
|
||||
|
||||
active_style.set_background_color(theme::color::BGOverlay);
|
||||
add_style(&active_style, active_state_style_name);
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
auto label = context.create_element<Label>(this, input_ctx->name, LabelStyle::Normal);
|
||||
label->set_flex_grow(2.0f);
|
||||
label->set_flex_shrink(1.0f);
|
||||
label->set_flex_basis(300.0f);
|
||||
label->set_height_auto();
|
||||
// TODO: whitespace nowrap impl
|
||||
|
||||
auto bindings_container = context.create_element<Element>(this, 0, "div", false);
|
||||
{
|
||||
bindings_container->set_display(Display::Flex);
|
||||
bindings_container->set_position(Position::Relative);
|
||||
bindings_container->set_flex_grow(2.0f);
|
||||
bindings_container->set_flex_shrink(1.0f);
|
||||
bindings_container->set_flex_basis(400.0f);
|
||||
bindings_container->set_flex_direction(FlexDirection::Row);
|
||||
bindings_container->set_align_items(AlignItems::Center);
|
||||
bindings_container->set_justify_content(JustifyContent::SpaceBetween);
|
||||
bindings_container->set_width(100.0f, Unit::Percent);
|
||||
bindings_container->set_height(56.0f);
|
||||
bindings_container->set_padding_right(12.0f);
|
||||
bindings_container->set_padding_left(4.0f);
|
||||
bindings_container->set_gap(4.0f);
|
||||
|
||||
for (size_t i = 0; i < recomp::bindings_per_input; i++) {
|
||||
BindingButton *binding_button = context.create_element<BindingButton>(bindings_container, "");
|
||||
binding_button->add_pressed_callback([this, i, on_bind_click]() {
|
||||
on_bind_click(this->input_id, i);
|
||||
});
|
||||
binding_buttons.push_back(binding_button);
|
||||
}
|
||||
}
|
||||
|
||||
if (input_ctx->clearable) {
|
||||
auto clear_button = context.create_element<IconButton>(this, "icons/Trash.svg", ButtonStyle::Danger, IconButtonSize::Large);
|
||||
clear_button->add_pressed_callback([this, on_clear_or_reset]() {
|
||||
on_clear_or_reset(this->input_id, false);
|
||||
});
|
||||
} else {
|
||||
auto reset_button = context.create_element<IconButton>(this, "icons/Reset.svg", ButtonStyle::Warning, IconButtonSize::Large);
|
||||
reset_button->add_pressed_callback([this, on_clear_or_reset]() {
|
||||
on_clear_or_reset(this->input_id, true);
|
||||
});
|
||||
}
|
||||
|
||||
bindings.resize(recomp::bindings_per_input);
|
||||
for (size_t i = 0; i < recomp::bindings_per_input; i++) {
|
||||
bindings[i] = recompinput::InputField();
|
||||
}
|
||||
}
|
||||
|
||||
GameInputRow::~GameInputRow() {
|
||||
}
|
||||
|
||||
void GameInputRow::update_bindings(BindingList &new_bindings) {
|
||||
for (size_t i = 0; i < new_bindings.size(); i++) {
|
||||
binding_buttons[i]->set_is_binding(false);
|
||||
|
||||
// skip update if no changes
|
||||
if (
|
||||
new_bindings[i].input_id == bindings[i].input_id &&
|
||||
new_bindings[i].input_type == bindings[i].input_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
binding_buttons[i]->set_binding(new_bindings[i].to_string());
|
||||
bindings[i] = new_bindings[i];
|
||||
}
|
||||
}
|
||||
|
||||
void GameInputRow::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Hover:
|
||||
{
|
||||
bool hover_active = std::get<EventHover>(e.variant).active;
|
||||
set_style_enabled(active_state_style_name, hover_active);
|
||||
if (hover_active && on_hover_callback) {
|
||||
on_hover_callback();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigPageControls::ConfigPageControls(
|
||||
Element *parent,
|
||||
int num_players,
|
||||
std::vector<GameInputContext> game_input_contexts,
|
||||
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->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);
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
render_all();
|
||||
}
|
||||
|
||||
void ConfigPageControls::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Update:
|
||||
if (awaiting_binding && !recompinput::is_binding()) {
|
||||
awaiting_binding = false;
|
||||
update_control_mappings();
|
||||
}
|
||||
if (last_update_index != update_index) {
|
||||
last_update_index = update_index;
|
||||
render_all();
|
||||
}
|
||||
queue_update();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::force_update() {
|
||||
update_index++;
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_all() {
|
||||
set_first_navigation_element(nullptr);
|
||||
Element *header_focusable = render_header();
|
||||
render_body(header_focusable);
|
||||
render_footer();
|
||||
confirm_first_navigation_element();
|
||||
}
|
||||
|
||||
Element *ConfigPageControls::render_header() {
|
||||
if (!multiplayer_enabled) {
|
||||
hide_header();
|
||||
return nullptr;
|
||||
}
|
||||
Element *header_focusable = nullptr;
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
add_header();
|
||||
|
||||
// header left
|
||||
{
|
||||
auto header_left = header->get_left();
|
||||
header_left->clear_children();
|
||||
if (multiplayer_view_mappings) {
|
||||
auto profile_name = context.create_element<Label>(
|
||||
header_left,
|
||||
"Editing: " + recomp::get_input_profile_name(selected_profile_index),
|
||||
LabelStyle::Normal
|
||||
);
|
||||
} else {
|
||||
// Nothing rendered here as of now.. maybe single player toggle
|
||||
}
|
||||
}
|
||||
|
||||
// header right
|
||||
{
|
||||
auto header_right = header->get_right();
|
||||
header_right->clear_children();
|
||||
|
||||
if (multiplayer_view_mappings) {
|
||||
Button* go_back_button = context.create_element<Button>(header_right, "Go back", ButtonStyle::Tertiary);
|
||||
go_back_button->add_pressed_callback([this]() {
|
||||
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(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(header_focusable);
|
||||
} else {
|
||||
body->get_right()->set_display(Display::None);
|
||||
render_body_players(header_focusable);
|
||||
}
|
||||
}
|
||||
|
||||
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(header_focusable);
|
||||
}
|
||||
|
||||
// right side
|
||||
{
|
||||
body->get_right()->clear_children();
|
||||
description_container = context.create_element<Element>(body->get_right(), 0, "p", true);
|
||||
description_container->set_text(
|
||||
"Sometimes, the windows combine with the seams in a way\n"
|
||||
"That twitches on a peak at the place where the spirit was slain\n"
|
||||
"Hey, one foot leads to another\n"
|
||||
"Night's for sleep, blue curtains, covers, sequins in the eyes\n"
|
||||
"That's a fine time to dine\n"
|
||||
"Divine who's circling, feeding the cards to the midwives"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
player_grid->set_justify_content(JustifyContent::SpaceBetween);
|
||||
player_grid->set_align_items(AlignItems::Center);
|
||||
player_grid->set_width(100.0f, Unit::Percent);
|
||||
player_grid->set_height_auto();
|
||||
player_grid->set_gap(64.0f);
|
||||
|
||||
for (int i = 0; i < num_players; i++) {
|
||||
auto player_card = context.create_element<PlayerCard>(
|
||||
player_grid,
|
||||
i,
|
||||
false
|
||||
);
|
||||
player_card->set_on_select_profile_callback([this](int player_index, int profile_index) {
|
||||
this->on_select_player_profile(player_index, profile_index);
|
||||
});
|
||||
player_card->set_on_edit_profile_callback([this](int player_index) {
|
||||
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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_select_player_profile(int player_index, int profile_index) {
|
||||
auto& assigned_player = recompinput::get_assigned_player(player_index);
|
||||
recomp::InputDevice device = recompinput::get_assigned_player_input_device(player_index);
|
||||
if (device != recomp::InputDevice::COUNT) {
|
||||
recomp::set_input_profile_for_player(player_index, profile_index, device);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_edit_player_profile(int player_index) {
|
||||
selected_player = player_index;
|
||||
recomp::InputDevice device = recompinput::get_assigned_player_input_device(player_index);
|
||||
if (device != recomp::InputDevice::COUNT) {
|
||||
selected_profile_index = recomp::get_input_profile_for_player(player_index, device);
|
||||
multiplayer_view_mappings = true;
|
||||
force_update();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigPageControls::render_footer() {
|
||||
if (multiplayer_enabled && !multiplayer_view_mappings) {
|
||||
hide_footer();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
if (!multiplayer_enabled) {
|
||||
keyboard_toggle = context.create_element<Toggle>(footer_left);
|
||||
keyboard_toggle->set_checked(single_player_show_keyboard_mappings);
|
||||
keyboard_toggle->add_checked_callback([this](bool checked) {
|
||||
this->single_player_show_keyboard_mappings = checked;
|
||||
this->update_control_mappings();
|
||||
});
|
||||
Label *kb_label = context.create_element<Label>(footer_left, "Enable keyboard", LabelStyle::Normal);
|
||||
kb_label->set_margin_left(12.0f);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto footer_right = footer->get_right();
|
||||
footer_right->clear_children();
|
||||
auto reset_to_defaults_button = context.create_element<Button>(footer_right, "Reset to defaults", ButtonStyle::Warning);
|
||||
reset_to_defaults_button->add_pressed_callback([this]() {
|
||||
recomp::reset_profile_bindings(this->selected_profile_index, this->get_player_input_device());
|
||||
this->update_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();
|
||||
|
||||
body_left->set_display(Display::Block);
|
||||
body_left->set_position(Position::Relative);
|
||||
body_left->set_height(100.0f, Unit::Percent);
|
||||
|
||||
{
|
||||
auto body_left_scroll = context.create_element<Element>(body_left, 0, "div", false);
|
||||
body_left_scroll->set_display(Display::Block);
|
||||
body_left_scroll->set_width(100.0f, Unit::Percent);
|
||||
body_left_scroll->set_max_height(100.0f, Unit::Percent);
|
||||
body_left_scroll->set_overflow_y(Overflow::Scroll);
|
||||
body_left_scroll->set_as_navigation_container(NavigationType::GridCol);
|
||||
|
||||
game_input_rows.clear();
|
||||
for (int i = 0; i < game_input_contexts.size(); i++) {
|
||||
auto &ctx = game_input_contexts[i];
|
||||
GameInputRow *row = context.create_element<GameInputRow>(
|
||||
body_left_scroll,
|
||||
&ctx,
|
||||
[this, i]() {
|
||||
this->on_option_hover(i);
|
||||
},
|
||||
[this](recompinput::GameInput game_input, int input_index) {
|
||||
this->on_bind_click(game_input, input_index);
|
||||
},
|
||||
[this](recompinput::GameInput game_input, bool reset) {
|
||||
this->on_clear_or_reset_game_input(game_input, reset);
|
||||
}
|
||||
);
|
||||
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();
|
||||
}
|
||||
|
||||
void ConfigPageControls::update_control_mappings() {
|
||||
if (!multiplayer_enabled) {
|
||||
selected_player = 0;
|
||||
selected_profile_index = single_player_show_keyboard_mappings
|
||||
? recomp::get_sp_keyboard_profile_index()
|
||||
: recomp::get_sp_controller_profile_index();
|
||||
} else if (!multiplayer_view_mappings) {
|
||||
return;
|
||||
}
|
||||
|
||||
game_input_bindings.clear();
|
||||
for (int i = 0; i < game_input_contexts.size(); i++) {
|
||||
GameInputContext &ctx = game_input_contexts[i];
|
||||
game_input_bindings[ctx.input_id] = {};
|
||||
|
||||
for (int j = 0; j < recomp::bindings_per_input; j++) {
|
||||
game_input_bindings[ctx.input_id].push_back(recomp::get_input_binding(selected_profile_index, ctx.input_id, j));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < game_input_rows.size(); i++) {
|
||||
game_input_rows[i]->update_bindings(
|
||||
game_input_bindings.at(game_input_rows[i]->get_input_id())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigPageControls::~ConfigPageControls() {
|
||||
controls_page = nullptr;
|
||||
}
|
||||
|
||||
recompinput::InputDevice ConfigPageControls::get_player_input_device() {
|
||||
if (multiplayer_enabled) {
|
||||
return recompinput::get_assigned_player_input_device(this->selected_player);
|
||||
}
|
||||
|
||||
return single_player_show_keyboard_mappings
|
||||
? recomp::InputDevice::Keyboard
|
||||
: recomp::InputDevice::Controller;
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_bind_click(recompinput::GameInput game_input, int input_index) {
|
||||
recompinput::InputDevice device = get_player_input_device();
|
||||
|
||||
recompinput::start_scanning_for_binding(this->selected_player, game_input, input_index, device);
|
||||
awaiting_binding = true;
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_clear_or_reset_game_input(recompinput::GameInput game_input, bool reset) {
|
||||
if (!reset) {
|
||||
recomp::clear_input_binding(selected_profile_index, game_input);
|
||||
} else {
|
||||
recompinput::InputDevice device = get_player_input_device();
|
||||
recomp::reset_input_binding(selected_profile_index, device, game_input);
|
||||
}
|
||||
update_control_mappings();
|
||||
}
|
||||
|
||||
void ConfigPageControls::set_selected_player(int player) {
|
||||
selected_player = player;
|
||||
}
|
||||
|
||||
void ConfigPageControls::on_option_hover(uint8_t index) {
|
||||
if (description_container) {
|
||||
description_container->set_text(game_input_contexts[index].description);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,140 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../elements/ui_config_page.h"
|
||||
#include "recomp_input.h"
|
||||
#include "../elements/ui_icon_button.h"
|
||||
#include "../elements/ui_binding_button.h"
|
||||
#include "../elements/ui_pill_button.h"
|
||||
#include "../elements/ui_toggle.h"
|
||||
#include "../elements/ui_modal.h"
|
||||
#include "../ui_player_card.h"
|
||||
|
||||
// TODO: remove after moving to recompinput
|
||||
namespace recompinput {
|
||||
using GameInput = recomp::GameInput;
|
||||
using InputField = recomp::InputField;
|
||||
using InputDevice = recomp::InputDevice;
|
||||
|
||||
constexpr int num_binding_slots = 4;
|
||||
} // recompinput
|
||||
|
||||
namespace recompui {
|
||||
|
||||
struct GameInputContext {
|
||||
std::string name;
|
||||
std::string description;
|
||||
recompinput::GameInput input_id;
|
||||
bool clearable;
|
||||
};
|
||||
|
||||
using BindingList = std::vector<recompinput::InputField>;
|
||||
// Receives which GameInput to be bound to, and the index of the binding that was clicked
|
||||
using on_bind_click_callback = std::function<void(recompinput::GameInput, int)>;
|
||||
// Player index, GameInput to be bound to, and the index of the binding that is being assigned
|
||||
using on_player_bind_callback = std::function<void(int, recompinput::GameInput, int)>;
|
||||
|
||||
using on_clear_or_reset_callback = std::function<void(recompinput::GameInput, bool)>;
|
||||
|
||||
// One single row of a game input mapping
|
||||
class GameInputRow : public Element {
|
||||
protected:
|
||||
recompinput::GameInput input_id;
|
||||
BindingList bindings;
|
||||
|
||||
int active_binding_index = -1;
|
||||
bool is_binding = false;
|
||||
|
||||
std::vector<BindingButton*> binding_buttons = {};
|
||||
|
||||
Style active_style;
|
||||
std::function<void()> on_hover_callback;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "GameInputRow"; }
|
||||
public:
|
||||
GameInputRow(
|
||||
Element *parent,
|
||||
GameInputContext *input_ctx,
|
||||
std::function<void()> on_hover_callback,
|
||||
on_bind_click_callback on_bind_click,
|
||||
on_clear_or_reset_callback on_clear_or_reset
|
||||
);
|
||||
virtual ~GameInputRow();
|
||||
void update_bindings(BindingList &new_bindings);
|
||||
recompinput::GameInput get_input_id() const { return input_id; }
|
||||
};
|
||||
|
||||
using PlayerBindings = std::map<recompinput::GameInput, BindingList>;
|
||||
|
||||
class ConfigPageControls : public ConfigPage {
|
||||
protected:
|
||||
// for tracking forced updates to entire page (major changes like player reassignment or singleplayer mode)
|
||||
int last_update_index = 0;
|
||||
int update_index = 0;
|
||||
|
||||
int selected_player = 0;
|
||||
int selected_profile_index = -1;
|
||||
int num_players;
|
||||
|
||||
bool multiplayer_enabled;
|
||||
bool multiplayer_view_mappings;
|
||||
|
||||
bool single_player_show_keyboard_mappings = false;
|
||||
|
||||
bool awaiting_binding = false;
|
||||
|
||||
std::vector<GameInputContext> game_input_contexts;
|
||||
PlayerBindings game_input_bindings;
|
||||
|
||||
std::vector<PlayerCard*> player_cards;
|
||||
std::vector<GameInputRow*> game_input_rows;
|
||||
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"; }
|
||||
private:
|
||||
void on_option_hover(uint8_t index);
|
||||
void on_bind_click(recompinput::GameInput game_input, int input_index);
|
||||
void on_clear_or_reset_game_input(
|
||||
recompinput::GameInput game_input,
|
||||
bool reset = false
|
||||
);
|
||||
|
||||
void on_select_player_profile(int player_index, int profile_index);
|
||||
void on_edit_player_profile(int player_index);
|
||||
|
||||
void render_all();
|
||||
// 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,
|
||||
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(Element *header_focusable);
|
||||
void update_control_mappings();
|
||||
void set_selected_player(int player);
|
||||
};
|
||||
|
||||
extern ConfigPageControls *controls_page;
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,307 +0,0 @@
|
||||
#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
|
||||
@@ -1,71 +0,0 @@
|
||||
#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
|
||||
@@ -1,29 +0,0 @@
|
||||
#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
|
||||
@@ -1,16 +0,0 @@
|
||||
#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
|
||||
@@ -1,385 +0,0 @@
|
||||
#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
|
||||
@@ -1,19 +0,0 @@
|
||||
#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
|
||||
@@ -1,109 +0,0 @@
|
||||
#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
|
||||
@@ -1,22 +0,0 @@
|
||||
#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
|
||||
@@ -1,772 +0,0 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
|
||||
#include "slot_map.h"
|
||||
#include "RmlUi/Core/StreamMemory.h"
|
||||
#include "RmlUi/../../Source/Core/DocumentHeader.h"
|
||||
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
#include "recomp_ui.h"
|
||||
#include "ui_context.h"
|
||||
#include "../elements/ui_element.h"
|
||||
|
||||
// Hash implementations for ContextId and ResourceId.
|
||||
template <>
|
||||
struct std::hash<recompui::ContextId> {
|
||||
std::size_t operator()(const recompui::ContextId& id) const {
|
||||
return std::hash<uint32_t>()(id.slot_id);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<recompui::ResourceId> {
|
||||
std::size_t operator()(const recompui::ResourceId& id) const {
|
||||
return std::hash<uint32_t>()(id.slot_id);
|
||||
}
|
||||
};
|
||||
|
||||
using resource_slotmap = dod::slot_map32<std::unique_ptr<recompui::Style>>;
|
||||
|
||||
namespace recompui {
|
||||
struct Context {
|
||||
std::mutex context_lock;
|
||||
resource_slotmap resources;
|
||||
Rml::ElementDocument* document;
|
||||
Element root_element;
|
||||
Element* autofocus_element = nullptr;
|
||||
std::vector<Element*> loose_elements;
|
||||
std::unordered_set<ResourceId> to_update;
|
||||
std::vector<std::tuple<Element*, ResourceId, std::string>> to_set_text;
|
||||
bool captures_input = true;
|
||||
bool captures_mouse = true;
|
||||
Context(Rml::ElementDocument* document) : document(document), root_element(document) {}
|
||||
};
|
||||
} // namespace recompui
|
||||
|
||||
using context_slotmap = dod::slot_map32<recompui::Context>;
|
||||
|
||||
static struct {
|
||||
std::recursive_mutex all_contexts_lock;
|
||||
context_slotmap all_contexts;
|
||||
std::unordered_set<recompui::ContextId> opened_contexts;
|
||||
std::unordered_map<Rml::ElementDocument*, recompui::ContextId> documents_to_contexts;
|
||||
Rml::SharedPtr<Rml::StyleSheetContainer> style_sheet;
|
||||
} context_state;
|
||||
|
||||
thread_local recompui::Context* opened_context = nullptr;
|
||||
thread_local recompui::ContextId opened_context_id = recompui::ContextId::null();
|
||||
|
||||
enum class ContextErrorType {
|
||||
OpenWithoutClose,
|
||||
OpenInvalidContext,
|
||||
CloseWithoutOpen,
|
||||
CloseWrongContext,
|
||||
DestroyInvalidContext,
|
||||
GetContextWithoutOpen,
|
||||
AddResourceWithoutOpen,
|
||||
AddResourceToWrongContext,
|
||||
UpdateElementWithoutContext,
|
||||
UpdateElementInWrongContext,
|
||||
SetTextElementWithoutContext,
|
||||
SetTextElementInWrongContext,
|
||||
GetResourceWithoutOpen,
|
||||
GetResourceFailed,
|
||||
DestroyResourceWithoutOpen,
|
||||
DestroyResourceInWrongContext,
|
||||
DestroyResourceNotFound,
|
||||
GetDocumentInvalidContext,
|
||||
GetAutofocusInvalidContext,
|
||||
SetAutofocusInvalidContext,
|
||||
InternalError,
|
||||
};
|
||||
|
||||
enum class SlotTag : uint8_t {
|
||||
Style = 0,
|
||||
Element = 1,
|
||||
};
|
||||
|
||||
void context_error(recompui::ContextId id, ContextErrorType type) {
|
||||
(void)id;
|
||||
|
||||
const char* error_message = "";
|
||||
|
||||
switch (type) {
|
||||
case ContextErrorType::OpenWithoutClose:
|
||||
error_message = "Attempted to open a UI context without closing another UI context";
|
||||
break;
|
||||
case ContextErrorType::OpenInvalidContext:
|
||||
error_message = "Attempted to open an invalid UI context";
|
||||
break;
|
||||
case ContextErrorType::CloseWithoutOpen:
|
||||
error_message = "Attempted to close a UI context without one being open";
|
||||
break;
|
||||
case ContextErrorType::CloseWrongContext:
|
||||
error_message = "Attempted to close a different UI context than the one that's open";
|
||||
break;
|
||||
case ContextErrorType::DestroyInvalidContext:
|
||||
error_message = "Attempted to destroy an invalid UI element";
|
||||
break;
|
||||
case ContextErrorType::GetContextWithoutOpen:
|
||||
error_message = "Attempted to get the current UI context with no UI context open";
|
||||
break;
|
||||
case ContextErrorType::AddResourceWithoutOpen:
|
||||
error_message = "Attempted to create a UI resource with no open UI context";
|
||||
break;
|
||||
case ContextErrorType::AddResourceToWrongContext:
|
||||
error_message = "Attempted to create a UI resource in a different UI context than the one that's open";
|
||||
break;
|
||||
case ContextErrorType::UpdateElementWithoutContext:
|
||||
error_message = "Attempted to update a UI element with no open UI context";
|
||||
break;
|
||||
case ContextErrorType::UpdateElementInWrongContext:
|
||||
error_message = "Attempted to update a UI element in a different UI context than the one that's open";
|
||||
break;
|
||||
case ContextErrorType::SetTextElementWithoutContext:
|
||||
error_message = "Attempted to set the text of a UI element with no open UI context";
|
||||
break;
|
||||
case ContextErrorType::SetTextElementInWrongContext:
|
||||
error_message = "Attempted to set the text of a UI element in a different UI context than the one that's open";
|
||||
break;
|
||||
case ContextErrorType::GetResourceWithoutOpen:
|
||||
error_message = "Attempted to get a UI resource with no open UI context";
|
||||
break;
|
||||
case ContextErrorType::GetResourceFailed:
|
||||
error_message = "Failed to get a UI resource from the current open UI context";
|
||||
break;
|
||||
case ContextErrorType::DestroyResourceWithoutOpen:
|
||||
error_message = "Attempted to destroy a UI resource with no open UI context";
|
||||
break;
|
||||
case ContextErrorType::DestroyResourceInWrongContext:
|
||||
error_message = "Attempted to destroy a UI resource in a different UI context than the one that's open";
|
||||
break;
|
||||
case ContextErrorType::DestroyResourceNotFound:
|
||||
error_message = "Attempted to destroy a UI resource that doesn't exist in the current context";
|
||||
break;
|
||||
case ContextErrorType::GetDocumentInvalidContext:
|
||||
error_message = "Attempted to get the document of an invalid UI context";
|
||||
break;
|
||||
case ContextErrorType::GetAutofocusInvalidContext:
|
||||
error_message = "Attempted to get the autofocus element of an invalid UI context";
|
||||
break;
|
||||
case ContextErrorType::SetAutofocusInvalidContext:
|
||||
error_message = "Attempted to set the autofocus element of an invalid UI context";
|
||||
break;
|
||||
case ContextErrorType::InternalError:
|
||||
error_message = "Internal error in UI context";
|
||||
break;
|
||||
default:
|
||||
error_message = "Unknown UI context error";
|
||||
break;
|
||||
}
|
||||
|
||||
// This assumes the error is coming from a mod, as it's unlikely that an end user will see a UI context error
|
||||
// in the base recomp.
|
||||
recompui::message_box((std::string{"Fatal error in mod - "} + error_message + ".").c_str());
|
||||
assert(false);
|
||||
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||
}
|
||||
|
||||
recompui::ContextId create_context_impl(Rml::ElementDocument* document) {
|
||||
static Rml::ElementDocument dummy_document{""};
|
||||
bool add_to_dict = true;
|
||||
|
||||
if (document == nullptr) {
|
||||
document = &dummy_document;
|
||||
add_to_dict = false;
|
||||
}
|
||||
|
||||
recompui::ContextId ret;
|
||||
{
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
ret = { context_state.all_contexts.emplace(document).raw };
|
||||
|
||||
if (add_to_dict) {
|
||||
context_state.documents_to_contexts.emplace(document, ret);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void recompui::init_styling(const std::filesystem::path& rcss_file) {
|
||||
std::string style{};
|
||||
{
|
||||
std::ifstream style_stream{rcss_file};
|
||||
style_stream.seekg(0, std::ios::end);
|
||||
style.resize(style_stream.tellg());
|
||||
style_stream.seekg(0, std::ios::beg);
|
||||
|
||||
style_stream.read(style.data(), style.size());
|
||||
}
|
||||
std::unique_ptr<Rml::StreamMemory> rml_stream = std::make_unique<Rml::StreamMemory>(reinterpret_cast<Rml::byte*>(style.data()), style.size());
|
||||
rml_stream->SetSourceURL(rcss_file.filename().string());
|
||||
context_state.style_sheet = Rml::Factory::InstanceStyleSheetStream(rml_stream.get());
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::create_context(const std::filesystem::path& path) {
|
||||
ContextId new_context = create_context_impl(nullptr);
|
||||
|
||||
auto workingdir = std::filesystem::current_path();
|
||||
|
||||
new_context.open();
|
||||
Rml::ElementDocument* doc = recompui::load_document(path.string());
|
||||
opened_context->document = doc;
|
||||
opened_context->root_element.base = doc;
|
||||
new_context.close();
|
||||
|
||||
{
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
context_state.documents_to_contexts.emplace(doc, new_context);
|
||||
}
|
||||
|
||||
return new_context;
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::create_context(Rml::ElementDocument* document) {
|
||||
assert(document != nullptr);
|
||||
|
||||
return create_context_impl(document);
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::create_context() {
|
||||
Rml::ElementDocument* doc = create_empty_document();
|
||||
doc->SetStyleSheetContainer(context_state.style_sheet);
|
||||
ContextId ret = create_context_impl(doc);
|
||||
Element* root = ret.get_root_element();
|
||||
// Mark the root element as not being a shim, as that's only needed for elements that were parented to Rml ones manually.
|
||||
root->shim = false;
|
||||
|
||||
ret.open();
|
||||
|
||||
// TODO: Utilize existing headers (for full continuity between documents) or get absolute path of assets.
|
||||
Rml::DocumentHeader header = Rml::DocumentHeader();
|
||||
header.source = "assets/";
|
||||
doc->ProcessHeader(&header);
|
||||
|
||||
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();
|
||||
|
||||
doc->Hide();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void recompui::destroy_context(ContextId id) {
|
||||
bool existed = false;
|
||||
|
||||
// TODO prevent deletion of a context while its mutex is in use. Second lock on the context's mutex before popping
|
||||
// from the slotmap?
|
||||
|
||||
// Check if the provided id exists.
|
||||
{
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
// Check if the target context is currently open.
|
||||
existed = context_state.all_contexts.has_key(context_slotmap::key{ id.slot_id });
|
||||
}
|
||||
|
||||
|
||||
// Raise an error if the context didn't exist.
|
||||
if (!existed) {
|
||||
context_error(id, ContextErrorType::DestroyInvalidContext);
|
||||
}
|
||||
|
||||
id.open();
|
||||
id.clear_children();
|
||||
id.close();
|
||||
|
||||
// Delete the provided id.
|
||||
{
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
context_state.all_contexts.erase(context_slotmap::key{ id.slot_id });
|
||||
}
|
||||
}
|
||||
|
||||
void recompui::destroy_all_contexts() {
|
||||
recompui::hide_all_contexts();
|
||||
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
// TODO prevent deletion of a context while its mutex is in use. Second lock on the context's mutex before popping
|
||||
// from the slotmap
|
||||
|
||||
std::vector<context_slotmap::key> keys{};
|
||||
for (const auto& [key, item] : context_state.all_contexts.items()) {
|
||||
keys.push_back(key);
|
||||
}
|
||||
|
||||
for (auto key : keys) {
|
||||
Context* ctx = context_state.all_contexts.get(key);
|
||||
|
||||
std::lock_guard context_lock{ ctx->context_lock };
|
||||
opened_context = ctx;
|
||||
opened_context_id = ContextId{ key };
|
||||
|
||||
opened_context_id.clear_children();
|
||||
|
||||
opened_context = nullptr;
|
||||
opened_context_id = ContextId::null();
|
||||
}
|
||||
|
||||
context_state.all_contexts.reset();
|
||||
context_state.documents_to_contexts.clear();
|
||||
}
|
||||
|
||||
void recompui::ContextId::open() {
|
||||
// Ensure no other context is opened by this thread already.
|
||||
if (opened_context_id != ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::OpenWithoutClose);
|
||||
}
|
||||
|
||||
// Get the context with this id.
|
||||
Context* ctx;
|
||||
{
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
// If the context was found, add it to the opened contexts.
|
||||
if (ctx != nullptr) {
|
||||
context_state.opened_contexts.emplace(*this);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the context exists.
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::OpenInvalidContext);
|
||||
}
|
||||
|
||||
// Take ownership of the target context.
|
||||
ctx->context_lock.lock();
|
||||
opened_context = ctx;
|
||||
opened_context_id = *this;
|
||||
}
|
||||
|
||||
bool recompui::ContextId::open_if_not_already() {
|
||||
if (opened_context_id == *this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
open();
|
||||
return true;
|
||||
}
|
||||
|
||||
void recompui::ContextId::close() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::CloseWithoutOpen);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::CloseWrongContext);
|
||||
}
|
||||
|
||||
// Release ownership of the target context.
|
||||
opened_context->context_lock.unlock();
|
||||
opened_context = nullptr;
|
||||
opened_context_id = ContextId::null();
|
||||
|
||||
// Remove this context from the opened contexts.
|
||||
{
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
context_state.opened_contexts.erase(*this);
|
||||
}
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::try_close_current_context() {
|
||||
if (opened_context_id != ContextId::null()) {
|
||||
ContextId prev_context = opened_context_id;
|
||||
opened_context_id.close();
|
||||
return prev_context;
|
||||
}
|
||||
return ContextId::null();
|
||||
}
|
||||
|
||||
void recompui::ContextId::process_updates() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::InternalError);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::InternalError);
|
||||
}
|
||||
|
||||
// Move the current update set into a local variable. This clears the update set
|
||||
// and allows it to be used to queue updates from any element callbacks.
|
||||
std::unordered_set<ResourceId> to_update = std::move(opened_context->to_update);
|
||||
|
||||
Event update_event = Event::update_event();
|
||||
|
||||
for (auto cur_resource_id : to_update) {
|
||||
resource_slotmap::key cur_key{ cur_resource_id.slot_id };
|
||||
|
||||
// Ignore any resources that aren't elements.
|
||||
if (cur_key.get_tag() != static_cast<uint8_t>(SlotTag::Element)) {
|
||||
// Assert to catch errors of queueing other resource types for update.
|
||||
// This isn't an actual error, so there's no issue with continuing in release builds.
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the resource being updaten from the context.
|
||||
std::unique_ptr<Style>* cur_resource = opened_context->resources.get(cur_key);
|
||||
|
||||
// Make sure the resource exists before dispatching the event. It may have been deleted
|
||||
// after being queued for a update, so just continue to the next element if it doesn't exist.
|
||||
if (cur_resource == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
static_cast<Element*>(cur_resource->get())->handle_event(update_event);
|
||||
}
|
||||
|
||||
std::vector<std::tuple<Element*, ResourceId, std::string>> to_set_text = std::move(opened_context->to_set_text);
|
||||
|
||||
// Delete the Rml elements that are pending deletion.
|
||||
for (auto cur_text_update : to_set_text) {
|
||||
Element* element_ptr = std::get<0>(cur_text_update);
|
||||
ResourceId resource = std::get<1>(cur_text_update);
|
||||
std::string& text = std::get<2>(cur_text_update);
|
||||
|
||||
// If the resource ID is valid, prefer that as we can quickly validate if the resource still exists.
|
||||
if (resource != ResourceId::null()) {
|
||||
resource_slotmap::key cur_key{ resource.slot_id };
|
||||
std::unique_ptr<Style>* cur_resource = opened_context->resources.get(cur_key);
|
||||
|
||||
// Make sure the resource exists before setting its text, as it may have been deleted.
|
||||
if (cur_resource == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Perform the text update.
|
||||
static_cast<Element*>(cur_resource->get())->base->SetInnerRML(text);
|
||||
}
|
||||
// Otherwise we use the element pointer, but we need to validate that it still exists before doing so.
|
||||
else {
|
||||
// Scan the current resources to find the target element.
|
||||
for (const std::unique_ptr<Style>& cur_e : opened_context->resources) {
|
||||
if (cur_e.get() == element_ptr) {
|
||||
element_ptr->base->SetInnerRML(text);
|
||||
// We can stop after finding the element.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool recompui::ContextId::captures_input() {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return ctx->captures_input;
|
||||
|
||||
}
|
||||
|
||||
bool recompui::ContextId::captures_mouse() {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return ctx->captures_mouse;
|
||||
}
|
||||
|
||||
void recompui::ContextId::set_captures_input(bool captures_input) {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
return;
|
||||
}
|
||||
ctx->captures_input = captures_input;
|
||||
}
|
||||
|
||||
void recompui::ContextId::set_captures_mouse(bool captures_mouse) {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
return;
|
||||
}
|
||||
ctx->captures_mouse = captures_mouse;
|
||||
}
|
||||
|
||||
recompui::Style* recompui::ContextId::add_resource_impl(std::unique_ptr<Style>&& resource) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
bool is_element = resource->is_element();
|
||||
Style* resource_ptr = resource.get();
|
||||
auto key = opened_context->resources.emplace(std::move(resource));
|
||||
|
||||
if (is_element) {
|
||||
Element* element_ptr = static_cast<Element*>(resource_ptr);
|
||||
element_ptr->set_id(std::string{element_ptr->get_type_name()} + "-" + std::to_string(key.raw));
|
||||
key.set_tag(static_cast<uint8_t>(SlotTag::Element));
|
||||
// Send one update to the element.
|
||||
opened_context->to_update.emplace(ResourceId{ key.raw });
|
||||
}
|
||||
else {
|
||||
key.set_tag(static_cast<uint8_t>(SlotTag::Style));
|
||||
}
|
||||
|
||||
resource_ptr->resource_id = { key.raw };
|
||||
return resource_ptr;
|
||||
}
|
||||
|
||||
void recompui::ContextId::add_loose_element(Element* 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);
|
||||
}
|
||||
|
||||
opened_context->loose_elements.emplace_back(element);
|
||||
}
|
||||
|
||||
void recompui::ContextId::queue_element_update(ResourceId element) {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::UpdateElementWithoutContext);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::UpdateElementInWrongContext);
|
||||
}
|
||||
|
||||
// Check that the element that was specified is in the open context.
|
||||
auto* elementPtr = opened_context->resources.get(resource_slotmap::key{ element.slot_id });
|
||||
if (elementPtr == nullptr) {
|
||||
context_error(*this, ContextErrorType::UpdateElementInWrongContext);
|
||||
}
|
||||
|
||||
opened_context->to_update.emplace(element);
|
||||
}
|
||||
|
||||
void recompui::ContextId::queue_set_text(Element* element, std::string&& text) {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::SetTextElementWithoutContext);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::SetTextElementInWrongContext);
|
||||
}
|
||||
|
||||
opened_context->to_set_text.emplace_back(std::make_tuple(element, element->resource_id, std::move(text)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void recompui::ContextId::destroy_resource(ResourceId resource) {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::DestroyResourceWithoutOpen);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::DestroyResourceInWrongContext);
|
||||
}
|
||||
|
||||
// Try to remove the resource from the current context.
|
||||
auto pop_result = opened_context->resources.pop(resource_slotmap::key{ resource.slot_id });
|
||||
if (!pop_result.has_value()) {
|
||||
context_error(*this, ContextErrorType::DestroyResourceNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
void recompui::ContextId::clear_children() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::DestroyResourceWithoutOpen);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::DestroyResourceInWrongContext);
|
||||
}
|
||||
|
||||
// Remove the root element's children.
|
||||
opened_context->root_element.clear_children();
|
||||
|
||||
// Remove any loose resources.
|
||||
for (Element* e : opened_context->loose_elements) {
|
||||
destroy_resource(e->resource_id);
|
||||
}
|
||||
opened_context->loose_elements.clear();
|
||||
}
|
||||
|
||||
Rml::ElementDocument* recompui::ContextId::get_document() {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
||||
}
|
||||
|
||||
return ctx->document;
|
||||
}
|
||||
|
||||
recompui::Element* recompui::ContextId::get_root_element() {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
||||
}
|
||||
|
||||
return &ctx->root_element;
|
||||
}
|
||||
|
||||
recompui::Element* recompui::ContextId::get_autofocus_element() {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::GetAutofocusInvalidContext);
|
||||
}
|
||||
|
||||
return ctx->autofocus_element;
|
||||
}
|
||||
|
||||
void recompui::ContextId::set_autofocus_element(Element* element) {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::SetAutofocusInvalidContext);
|
||||
}
|
||||
|
||||
ctx->autofocus_element = element;
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::get_current_context() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(ContextId::null(), ContextErrorType::GetContextWithoutOpen);
|
||||
}
|
||||
|
||||
return opened_context_id;
|
||||
}
|
||||
|
||||
recompui::Style* get_resource_from_current_context(resource_slotmap::key key) {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == recompui::ContextId::null()) {
|
||||
context_error(recompui::ContextId::null(), ContextErrorType::GetResourceWithoutOpen);
|
||||
}
|
||||
|
||||
auto* value = opened_context->resources.get(key);
|
||||
if (value == nullptr) {
|
||||
context_error(opened_context_id, ContextErrorType::GetResourceFailed);
|
||||
}
|
||||
|
||||
return value->get();
|
||||
}
|
||||
|
||||
const recompui::Style* recompui::ResourceId::operator*() const {
|
||||
resource_slotmap::key key{ slot_id };
|
||||
|
||||
return get_resource_from_current_context(key);
|
||||
}
|
||||
|
||||
recompui::Style* recompui::ResourceId::operator*() {
|
||||
resource_slotmap::key key{ slot_id };
|
||||
|
||||
return get_resource_from_current_context(key);
|
||||
}
|
||||
|
||||
const recompui::Element* recompui::ResourceId::as_element() const {
|
||||
resource_slotmap::key key{ slot_id };
|
||||
uint8_t tag = key.get_tag();
|
||||
|
||||
assert(tag == static_cast<uint8_t>(SlotTag::Element));
|
||||
|
||||
return static_cast<Element*>(get_resource_from_current_context(key));
|
||||
}
|
||||
|
||||
recompui::Element* recompui::ResourceId::as_element() {
|
||||
resource_slotmap::key key{ slot_id };
|
||||
uint8_t tag = key.get_tag();
|
||||
|
||||
assert(tag == static_cast<uint8_t>(SlotTag::Element));
|
||||
|
||||
return static_cast<Element*>(get_resource_from_current_context(key));
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::get_context_from_document(Rml::ElementDocument* document) {
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
auto find_it = context_state.documents_to_contexts.find(document);
|
||||
if (find_it == context_state.documents_to_contexts.end()) {
|
||||
return ContextId::null();
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
|
||||
#include "RmlUi/Core.h"
|
||||
|
||||
#include "ui_resource.h"
|
||||
|
||||
namespace recompui {
|
||||
class Style;
|
||||
class Element;
|
||||
class ContextId {
|
||||
Style* add_resource_impl(std::unique_ptr<Style>&& resource);
|
||||
public:
|
||||
uint32_t slot_id;
|
||||
auto operator<=>(const ContextId& rhs) const = default;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T* create_element(Args... args) {
|
||||
return static_cast<T*>(add_resource_impl(std::make_unique<T>(std::forward<Args>(args)...)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* create_element(T&& element) {
|
||||
return static_cast<T*>(add_resource_impl(std::make_unique<T>(std::move(element))));
|
||||
}
|
||||
|
||||
void add_loose_element(Element* element);
|
||||
void queue_element_update(ResourceId element);
|
||||
void queue_set_text(Element* element, std::string&& text);
|
||||
|
||||
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);
|
||||
|
||||
void open();
|
||||
bool open_if_not_already();
|
||||
void close();
|
||||
void process_updates();
|
||||
|
||||
static constexpr ContextId null() { return ContextId{ .slot_id = uint32_t(-1) }; }
|
||||
|
||||
bool captures_input();
|
||||
bool captures_mouse();
|
||||
|
||||
void set_captures_input(bool captures_input);
|
||||
void set_captures_mouse(bool captures_input);
|
||||
};
|
||||
|
||||
ContextId create_context(const std::filesystem::path& path);
|
||||
ContextId create_context(Rml::ElementDocument* document);
|
||||
ContextId create_context();
|
||||
void destroy_context(ContextId id);
|
||||
ContextId get_current_context();
|
||||
ContextId get_context_from_document(Rml::ElementDocument* document);
|
||||
void destroy_all_contexts();
|
||||
|
||||
void register_ui_exports();
|
||||
} // namespace recompui
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace recompui {
|
||||
class Style;
|
||||
class Element;
|
||||
struct ResourceId {
|
||||
uint32_t slot_id;
|
||||
|
||||
bool operator==(const ResourceId& rhs) const = default;
|
||||
|
||||
const Style* operator*() const;
|
||||
Style* operator*();
|
||||
|
||||
const Style* operator->() const { return *(*this); }
|
||||
Style* operator->() { return *(*this); }
|
||||
|
||||
const Element* as_element() const;
|
||||
Element* as_element();
|
||||
|
||||
static constexpr ResourceId null() { return ResourceId{ uint32_t(-1) }; }
|
||||
};
|
||||
} // namespace recompui
|
||||
@@ -1,207 +0,0 @@
|
||||
#include "ui_binding_button.h"
|
||||
#include "ui_theme.h"
|
||||
#include "recomp_ui.h"
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
namespace recompui {
|
||||
static const float padding = 8.0f;
|
||||
|
||||
BindingButton::BindingButton(Element *parent, const std::string &mapped_binding) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
|
||||
this->mapped_binding = mapped_binding;
|
||||
|
||||
enable_focus();
|
||||
apply_sizing_styling(this);
|
||||
|
||||
set_border_color(theme::color::WhiteA5);
|
||||
set_background_color(theme::color::WhiteA5);
|
||||
set_color(theme::color::TextDim);
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
|
||||
focus_style.set_border_color(theme::color::White);
|
||||
focus_style.set_background_color(theme::color::WhiteA30);
|
||||
focus_style.set_color(theme::color::TextActive);
|
||||
hover_style.set_border_color(theme::color::WhiteA80);
|
||||
hover_style.set_background_color(theme::color::WhiteA20);
|
||||
hover_style.set_color(theme::color::Text);
|
||||
|
||||
disabled_style.set_color(theme::color::TextDim);
|
||||
disabled_style.set_opacity(0.5f);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
bound_text_el = context.create_element<Element>(this, 0, "div", true);
|
||||
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);
|
||||
recording_edge = context.create_element<Element>(recording_parent);
|
||||
recording_svg = context.create_element<Svg>(recording_edge, "icons/RecordBorder.svg");
|
||||
apply_recording_style();
|
||||
}
|
||||
|
||||
void BindingButton::apply_sizing_styling(Element *el) {
|
||||
const float height = 56.0f - (theme::border::width * 2.0f);
|
||||
el->set_display(Display::Flex);
|
||||
el->set_position(Position::Relative);
|
||||
|
||||
el->set_flex_grow(1.0f);
|
||||
el->set_flex_shrink(1.0f);
|
||||
el->set_flex_basis(100.0f, recompui::Unit::Percent);
|
||||
|
||||
el->set_align_items(AlignItems::Center);
|
||||
el->set_justify_content(JustifyContent::Center);
|
||||
|
||||
el->set_width(100.0f, recompui::Unit::Percent);
|
||||
el->set_height(height);
|
||||
el->set_padding(padding);
|
||||
|
||||
el->set_border_width(theme::border::width);
|
||||
el->set_border_radius(theme::border::radius_sm);
|
||||
el->set_border_color(theme::color::Transparent);
|
||||
el->set_background_color(theme::color::Transparent);
|
||||
}
|
||||
|
||||
void BindingButton::apply_recording_style() {
|
||||
recording_parent->set_display(Display::Flex);
|
||||
recording_parent->set_position(Position::Absolute);
|
||||
|
||||
recording_parent->set_top(0.0f);
|
||||
recording_parent->set_left(0.0f);
|
||||
recording_parent->set_right(0.0f);
|
||||
recording_parent->set_bottom(0.0f);
|
||||
|
||||
recording_parent->set_align_items(AlignItems::Center);
|
||||
recording_parent->set_justify_content(JustifyContent::Center);
|
||||
recording_parent->set_opacity(0);
|
||||
|
||||
const float circle_size = 24;
|
||||
recording_circle->set_width(circle_size);
|
||||
recording_circle->set_height(circle_size);
|
||||
recording_circle->set_border_radius(circle_size * 0.5f);
|
||||
recording_circle->set_background_color(theme::color::Danger);
|
||||
|
||||
const float edge_size = 36;
|
||||
recording_edge->set_position(Position::Absolute);
|
||||
recording_edge->set_top(50.0f, recompui::Unit::Percent);
|
||||
recording_edge->set_left(50.0f, recompui::Unit::Percent);
|
||||
recording_edge->set_width(edge_size);
|
||||
recording_edge->set_height(edge_size);
|
||||
recording_edge->set_translate_2D(-50.0f, -50.0f, recompui::Unit::Percent);
|
||||
|
||||
recording_svg->set_width(edge_size);
|
||||
recording_svg->set_height(edge_size);
|
||||
recording_svg->set_image_color(theme::color::Danger);
|
||||
}
|
||||
|
||||
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(32.0f);
|
||||
bound_text_el->set_font_style(FontStyle::Normal);
|
||||
bound_text_el->set_font_weight(400);
|
||||
bound_text_el->set_line_height(32.0f);
|
||||
bound_text_el->set_opacity(1);
|
||||
}
|
||||
|
||||
void BindingButton::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
void BindingButton::set_binding(const std::string &binding) {
|
||||
this->mapped_binding = 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) {
|
||||
this->is_binding = is_binding;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void BindingButton::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
set_is_binding(!is_binding);
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
{
|
||||
if (is_binding == false) {
|
||||
break;
|
||||
}
|
||||
queue_update();
|
||||
std::chrono::high_resolution_clock::duration since_start = ultramodern::time_since_start();
|
||||
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_start).count();
|
||||
const float loop_length_seconds = 1.5f;
|
||||
float t = static_cast<float>(millis) / (loop_length_seconds * 1000.0f);
|
||||
float sine_time = sinf(t * 2.0f * 3.14159f);
|
||||
float scale = 1.0f + ((sine_time * 0.15f / 2.0f) - 0.15f);
|
||||
recording_circle->set_scale_2D(scale, scale);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
namespace recompui {
|
||||
class BindingButton : public Element {
|
||||
protected:
|
||||
bool is_binding = false;
|
||||
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;
|
||||
Svg *recording_svg;
|
||||
|
||||
Style binding_style;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "BindingButton"; }
|
||||
public:
|
||||
BindingButton(Element *parent, const std::string &mapped_binding);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
void set_binding(const std::string &binding);
|
||||
void set_is_binding(bool is_binding);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
// This is exposed for placeholder elements
|
||||
static void apply_sizing_styling(Element *el);
|
||||
private:
|
||||
void apply_recording_style();
|
||||
void apply_binding_style();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,182 +0,0 @@
|
||||
#include "ui_button.h"
|
||||
#include "ui_label.h"
|
||||
#include "ui_pseudo_border.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Button::Button(Element *parent, const std::string &text, ButtonStyle style, ButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button", false) {
|
||||
this->style = style;
|
||||
this->size = size;
|
||||
// Borders add width to the button, so this subtracts from the base size to bring it back to the expected size.
|
||||
float float_size_internal = static_cast<float>(size) - (theme::border::width * 2.0f);
|
||||
|
||||
float base_padding = 24.0f;
|
||||
switch (size) {
|
||||
case ButtonSize::Small:
|
||||
base_padding = 12.0f;
|
||||
break;
|
||||
case ButtonSize::Medium:
|
||||
base_padding = 12.0f;
|
||||
break;
|
||||
case ButtonSize::Large:
|
||||
default:
|
||||
base_padding = 24.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
const float button_padding_internal = base_padding - (theme::border::width * 2.0f);
|
||||
|
||||
enable_focus();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::Center);
|
||||
|
||||
set_padding_right(button_padding_internal);
|
||||
set_padding_left(button_padding_internal);
|
||||
|
||||
set_width_auto();
|
||||
set_height(float_size_internal);
|
||||
set_min_height(float_size_internal);
|
||||
set_max_height(float_size_internal);
|
||||
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(theme::border::radius_md);
|
||||
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case ButtonSize::Medium:
|
||||
context.create_element<Label>(this, text, LabelStyle::Small);
|
||||
break;
|
||||
case ButtonSize::Large:
|
||||
default:
|
||||
context.create_element<Label>(this, text, LabelStyle::Normal);
|
||||
break;
|
||||
}
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(theme::color::Text);
|
||||
set_tab_index(TabIndex::Auto);
|
||||
hover_style.set_color(theme::color::Text);
|
||||
focus_style.set_color(theme::color::Text);
|
||||
disabled_style.set_color(theme::color::TextDim, 128);
|
||||
hover_disabled_style.set_color(theme::color::Text, 128);
|
||||
|
||||
apply_button_style(style);
|
||||
|
||||
// transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||
}
|
||||
|
||||
void Button::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Button::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Button::apply_button_style(ButtonStyle new_style) {
|
||||
style = new_style;
|
||||
switch (style) {
|
||||
case ButtonStyle::Primary: {
|
||||
apply_theme_style(theme::color::Primary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Secondary: {
|
||||
apply_theme_style(theme::color::Secondary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Tertiary: {
|
||||
apply_theme_style(theme::color::Text);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Success: {
|
||||
apply_theme_style(theme::color::Success);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Warning: {
|
||||
apply_theme_style(theme::color::Warning);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Danger: {
|
||||
apply_theme_style(theme::color::Danger);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Basic: {
|
||||
apply_theme_style(theme::color::Text, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown button style.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Button::apply_theme_style(recompui::theme::color color, bool is_basic) {
|
||||
const uint8_t border_opacity = is_basic ? 0 : 204;
|
||||
const uint8_t background_opacity = is_basic ? 0 : 13;
|
||||
const uint8_t background_hover_opacity = 77;
|
||||
const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255;
|
||||
|
||||
set_border_color(color, border_opacity);
|
||||
set_background_color(color, background_opacity);
|
||||
hover_style.set_border_color(color, border_hover_opacity);
|
||||
hover_style.set_background_color(color, background_hover_opacity);
|
||||
focus_style.set_border_color(color, border_hover_opacity);
|
||||
focus_style.set_background_color(color, background_hover_opacity);
|
||||
disabled_style.set_border_color(color, border_opacity / 4);
|
||||
disabled_style.set_background_color(color, background_opacity / 4);
|
||||
hover_disabled_style.set_border_color(color, border_hover_opacity / 4);
|
||||
hover_disabled_style.set_background_color(color, background_hover_opacity / 4);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
}
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum class ButtonStyle {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Success,
|
||||
Warning,
|
||||
Danger,
|
||||
Basic, // No border, only shows background on hover or focus.
|
||||
};
|
||||
|
||||
enum class ButtonSize {
|
||||
Small = 32,
|
||||
Medium = 48,
|
||||
Large = 72,
|
||||
Default = Large
|
||||
};
|
||||
|
||||
class Button : public Element {
|
||||
protected:
|
||||
ButtonStyle style = ButtonStyle::Primary;
|
||||
ButtonSize size = ButtonSize::Default;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Button"; }
|
||||
public:
|
||||
Button(Element *parent, const std::string &text, ButtonStyle style, ButtonSize size = ButtonSize::Default);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void apply_button_style(ButtonStyle new_style);
|
||||
private:
|
||||
void apply_theme_style(recompui::theme::color color, bool is_basic = false);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,77 +0,0 @@
|
||||
#include "ui_clickable.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Clickable::Clickable(Element *parent, bool draggable) : Element(parent, Events(EventType::Click, EventType::MouseButton, EventType::Hover, EventType::Enable, draggable ? EventType::Drag : EventType::None)) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
if (draggable) {
|
||||
set_drag(Drag::Drag);
|
||||
}
|
||||
}
|
||||
|
||||
void Clickable::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click: {
|
||||
if (is_enabled()) {
|
||||
const EventClick &click = std::get<EventClick>(e.variant);
|
||||
for (const auto &function : clicked_callbacks) {
|
||||
function(click.x, click.y);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::MouseButton: {
|
||||
if (is_enabled()) {
|
||||
const EventMouseButton &mousebutton = std::get<EventMouseButton>(e.variant);
|
||||
if (mousebutton.button == MouseButton::Left && mousebutton.pressed) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function(mousebutton.x, mousebutton.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Drag: {
|
||||
if (is_enabled()) {
|
||||
const EventDrag &drag = std::get<EventDrag>(e.variant);
|
||||
for (const auto &function : dragged_callbacks) {
|
||||
function(drag.x, drag.y, drag.phase);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Clickable::add_clicked_callback(std::function<void(float, float)> callback) {
|
||||
clicked_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Clickable::add_pressed_callback(std::function<void(float, float)> callback) {
|
||||
pressed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Clickable::add_dragged_callback(std::function<void(float, float, DragPhase)> callback) {
|
||||
dragged_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Clickable : public Element {
|
||||
protected:
|
||||
std::vector<std::function<void(float, float)>> clicked_callbacks;
|
||||
std::vector<std::function<void(float, float)>> pressed_callbacks;
|
||||
std::vector<std::function<void(float, float, DragPhase)>> dragged_callbacks;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Clickable"; }
|
||||
public:
|
||||
Clickable(Element *parent, bool draggable = false);
|
||||
void add_clicked_callback(std::function<void(float, float)> callback);
|
||||
void add_pressed_callback(std::function<void(float, float)> callback);
|
||||
void add_dragged_callback(std::function<void(float, float, DragPhase)> callback);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,138 +0,0 @@
|
||||
#include "ui_config_page.h"
|
||||
#include "ui_theme.h"
|
||||
#include "ui_container.h"
|
||||
|
||||
|
||||
namespace recompui {
|
||||
static const float headerFooterPaddingVert = 20.0f;
|
||||
static const float headerFooterPaddingHorz = 20.0f;
|
||||
|
||||
static void set_header_footer_side_styles(Element *el) {
|
||||
el->set_align_items(AlignItems::Center);
|
||||
// el->set_width(100.0f, Unit::Percent);
|
||||
el->set_width_auto();
|
||||
el->set_height_auto();
|
||||
el->set_flex_basis_auto();
|
||||
el->set_gap(8.0f);
|
||||
}
|
||||
|
||||
ConfigHeaderFooter::ConfigHeaderFooter(Element *parent, bool is_header) : Element(parent, 0, "div", false) {
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::SpaceBetween);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height_auto();
|
||||
|
||||
set_padding_top(headerFooterPaddingVert);
|
||||
set_padding_bottom(headerFooterPaddingVert);
|
||||
set_padding_left(headerFooterPaddingHorz);
|
||||
set_padding_right(headerFooterPaddingHorz);
|
||||
|
||||
set_background_color(theme::color::BGShadow);
|
||||
|
||||
const float border_width = theme::border::width;
|
||||
const recompui::theme::color border_color = theme::color::BorderSoft;
|
||||
if (is_header) {
|
||||
set_border_bottom_width(border_width);
|
||||
set_border_bottom_color(border_color);
|
||||
} else {
|
||||
set_border_top_width(border_width);
|
||||
set_border_top_color(border_color);
|
||||
set_border_bottom_left_radius(theme::border::radius_lg);
|
||||
set_border_bottom_right_radius(theme::border::radius_lg);
|
||||
}
|
||||
|
||||
ContextId context = get_current_context();
|
||||
left = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexStart, 0);
|
||||
set_header_footer_side_styles(left);
|
||||
|
||||
right = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexEnd, 0);
|
||||
set_header_footer_side_styles(right);
|
||||
}
|
||||
|
||||
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);
|
||||
el->set_flex_shrink(1.0f);
|
||||
el->set_flex_basis(100.0f, Unit::Percent);
|
||||
el->set_width_auto();
|
||||
el->set_height_auto();
|
||||
el->set_padding(16);
|
||||
}
|
||||
|
||||
ConfigBody::ConfigBody(Element *parent) : Element(parent, 0, "div", false) {
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_flex_grow(1.0f);
|
||||
set_flex_shrink(1.0f);
|
||||
set_flex_basis_auto();
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
left = context.create_element<Element>(this, 0, "div", false);
|
||||
set_config_body_side_styles(left);
|
||||
|
||||
right = context.create_element<Element>(this, 0, "div", false);
|
||||
set_config_body_side_styles(right);
|
||||
}
|
||||
|
||||
ConfigPage::ConfigPage(Element *parent, uint32_t events_enabled) : Element(parent, events_enabled, "div", false) {
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
|
||||
set_flex_grow(1.0f);
|
||||
set_flex_shrink(1.0f);
|
||||
set_flex_basis(100.0f, Unit::Percent);
|
||||
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_justify_content(JustifyContent::SpaceBetween);
|
||||
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height(100.0f, Unit::Percent);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
header = context.create_element<ConfigHeaderFooter>(this, true);
|
||||
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->hide();
|
||||
}
|
||||
|
||||
ConfigHeaderFooter* ConfigPage::add_header() {
|
||||
header->show();
|
||||
return header;
|
||||
}
|
||||
|
||||
void ConfigPage::hide_header() {
|
||||
header->hide();
|
||||
}
|
||||
|
||||
ConfigHeaderFooter* ConfigPage::add_footer() {
|
||||
footer->show();
|
||||
set_border_bottom_left_radius(0);
|
||||
set_border_bottom_right_radius(0);
|
||||
return footer;
|
||||
}
|
||||
|
||||
void ConfigPage::hide_footer() {
|
||||
footer->hide();
|
||||
set_border_bottom_left_radius(theme::border::radius_lg);
|
||||
set_border_bottom_right_radius(theme::border::radius_lg);
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
class ConfigHeaderFooter : public Element {
|
||||
protected:
|
||||
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 {
|
||||
protected:
|
||||
Element *left;
|
||||
Element *right;
|
||||
|
||||
std::string_view get_type_name() override { return "ConfigBody"; }
|
||||
public:
|
||||
ConfigBody(Element *parent);
|
||||
Element *get_left() { return left; }
|
||||
Element *get_right() { return right; }
|
||||
};
|
||||
|
||||
|
||||
class ConfigPage : public Element {
|
||||
protected:
|
||||
ConfigHeaderFooter *header = nullptr;
|
||||
ConfigBody *body;
|
||||
ConfigHeaderFooter *footer = nullptr;
|
||||
|
||||
std::string_view get_type_name() override { return "ConfigPage"; }
|
||||
public:
|
||||
ConfigPage(Element *parent, uint32_t events_enabled = 0);
|
||||
ConfigHeaderFooter *add_header();
|
||||
void hide_header();
|
||||
ConfigHeaderFooter *add_footer();
|
||||
void hide_footer();
|
||||
ConfigHeaderFooter *get_header() { return header; };
|
||||
ConfigBody *get_body() { return body; };
|
||||
ConfigHeaderFooter *get_footer() { return footer; };
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "ui_container.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Container::Container(Element *parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled) : Element(parent, events_enabled) {
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(direction);
|
||||
set_justify_content(justify_content);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Container : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Container"; }
|
||||
public:
|
||||
Container(Element* parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled = 0);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,166 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_style.h"
|
||||
#include "../core/ui_context.h"
|
||||
|
||||
#include "recomp.h"
|
||||
#include <ultramodern/ultra64.h>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
namespace recompui {
|
||||
struct UICallback {
|
||||
ContextId context;
|
||||
PTR(void) callback;
|
||||
PTR(void) userdata;
|
||||
};
|
||||
|
||||
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);
|
||||
friend ContextId create_context();
|
||||
friend class ContextId; // To allow ContextId to call the handle_event method directly.
|
||||
private:
|
||||
Rml::Element *base = nullptr;
|
||||
Rml::ElementPtr base_owning = {};
|
||||
uint32_t events_enabled = 0;
|
||||
std::vector<Style *> styles;
|
||||
std::vector<uint32_t> styles_counter;
|
||||
std::unordered_set<std::string_view> style_active_set;
|
||||
std::unordered_multimap<std::string_view, uint32_t> style_name_index_map;
|
||||
std::vector<UICallback> callbacks;
|
||||
Element *parent = nullptr;
|
||||
std::vector<Element *> children;
|
||||
std::string id;
|
||||
bool shim = false;
|
||||
bool enabled = true;
|
||||
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);
|
||||
void apply_style(Style *style);
|
||||
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);
|
||||
virtual void process_event(const Event &e);
|
||||
virtual ElementValue get_element_value() { return std::monostate{}; }
|
||||
virtual void set_input_value(const ElementValue&) {}
|
||||
virtual std::string_view get_type_name() { return "Element"; }
|
||||
public:
|
||||
// Used for backwards compatibility with legacy UI elements.
|
||||
Element(Rml::Element *base);
|
||||
|
||||
// Used to actually construct elements.
|
||||
Element(Element* parent, uint32_t events_enabled = 0, Rml::String base_class = "div", bool can_set_text = false);
|
||||
virtual ~Element();
|
||||
void clear_children();
|
||||
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);
|
||||
void set_style_enabled(std::string_view style_name, bool enabled);
|
||||
bool is_style_enabled(std::string_view style_name);
|
||||
void apply_styles();
|
||||
bool is_element() override { return true; }
|
||||
float get_absolute_left();
|
||||
float get_absolute_top();
|
||||
float get_client_left();
|
||||
float get_client_top();
|
||||
float get_client_width();
|
||||
float get_client_height();
|
||||
void enable_focus();
|
||||
void focus();
|
||||
void blur();
|
||||
void queue_update();
|
||||
void register_callback(ContextId context, PTR(void) callback, PTR(void) userdata);
|
||||
uint32_t get_input_value_u32();
|
||||
float get_input_value_float();
|
||||
double get_input_value_double();
|
||||
void set_input_value_u32(uint32_t val) { set_input_value(val); }
|
||||
void set_input_value_float(float val) { set_input_value(val); }
|
||||
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);
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,181 +0,0 @@
|
||||
#include "ui_icon_button.h"
|
||||
#include "ui_pseudo_border.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
IconButton::IconButton(Element *parent, const std::string &svg_src, ButtonStyle style, IconButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
|
||||
this->style = style;
|
||||
this->size = size;
|
||||
// Borders add width to the button, so this subtracts from the base size to bring it back to the expected size.
|
||||
float float_size_internal = static_cast<float>(size) - (theme::border::width * 2.0f);
|
||||
|
||||
enable_focus();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_position(Position::Relative);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::Center);
|
||||
set_width(float_size_internal);
|
||||
set_min_width(float_size_internal);
|
||||
set_max_width(float_size_internal);
|
||||
set_height(float_size_internal);
|
||||
set_min_height(float_size_internal);
|
||||
set_max_height(float_size_internal);
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(float_size_internal * 0.5f);
|
||||
set_border_color(theme::color::Transparent);
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(theme::color::TextDim);
|
||||
set_tab_index(TabIndex::Auto);
|
||||
set_opacity(1.0f);
|
||||
|
||||
hover_style.set_color(theme::color::Text);
|
||||
focus_style.set_color(theme::color::Text);
|
||||
disabled_style.set_color(theme::color::TextDim);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
disabled_style.set_opacity(0.5f);
|
||||
hover_disabled_style.set_color(theme::color::TextDim);
|
||||
|
||||
float icon_size = 0;
|
||||
switch (size) {
|
||||
case IconButtonSize::Mini:
|
||||
icon_size = 16.0f;
|
||||
break;
|
||||
case IconButtonSize::Small:
|
||||
icon_size = 24.0f;
|
||||
break;
|
||||
case IconButtonSize::Medium:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case IconButtonSize::Large:
|
||||
default:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case IconButtonSize::XLarge:
|
||||
icon_size = 40.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
apply_button_style(style);
|
||||
|
||||
// transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out;
|
||||
}
|
||||
|
||||
void IconButton::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
{
|
||||
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||
set_style_enabled(hover_state, hover_active);
|
||||
svg->set_color(hover_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
svg->set_color(theme::color::TextDim);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
svg->set_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
svg->set_color(focus_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IconButton::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void IconButton::apply_button_style(ButtonStyle new_style) {
|
||||
style = new_style;
|
||||
switch (style) {
|
||||
case ButtonStyle::Primary: {
|
||||
apply_theme_style(theme::color::Primary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Secondary: {
|
||||
apply_theme_style(theme::color::Secondary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Tertiary: {
|
||||
apply_theme_style(theme::color::Text);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Success: {
|
||||
apply_theme_style(theme::color::Success);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Warning: {
|
||||
apply_theme_style(theme::color::Warning);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Danger: {
|
||||
apply_theme_style(theme::color::Danger);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Basic: {
|
||||
apply_theme_style(theme::color::Text, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown button style.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void IconButton::apply_theme_style(recompui::theme::color color, bool is_basic) {
|
||||
const uint8_t background_opacity = is_basic ? 0 : 13;
|
||||
const uint8_t border_opacity = is_basic ? 0 : 204;
|
||||
const uint8_t background_hover_opacity = 77;
|
||||
const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255;
|
||||
|
||||
set_border_color(color, border_opacity);
|
||||
set_background_color(color, background_opacity);
|
||||
hover_style.set_border_color(color, border_hover_opacity);
|
||||
hover_style.set_background_color(color, background_hover_opacity);
|
||||
focus_style.set_border_color(color, border_hover_opacity);
|
||||
focus_style.set_background_color(color, background_hover_opacity);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_button.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
namespace recompui {
|
||||
enum class IconButtonSize {
|
||||
Mini = 20, // 20x20 (Inline with body text)
|
||||
Small = 32, // 32x32
|
||||
Medium = 48, // 48x48
|
||||
Large = 56, // 56x56
|
||||
Default = IconButtonSize::Large,
|
||||
XLarge = 72, // 72x72
|
||||
};
|
||||
|
||||
class IconButton : public Element {
|
||||
protected:
|
||||
ButtonStyle style = ButtonStyle::Primary;
|
||||
IconButtonSize size = IconButtonSize::Default;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
Svg *svg;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "IconButton"; }
|
||||
public:
|
||||
IconButton(Element *parent, const std::string &svg_src, ButtonStyle style, IconButtonSize size = IconButtonSize::Default);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void apply_button_style(ButtonStyle new_style);
|
||||
private:
|
||||
void apply_theme_style(recompui::theme::color color, bool is_basic = false);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "ui_image.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Image::Image(Element *parent, std::string_view src) : Element(parent, 0, "img") {
|
||||
set_src(src);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Image : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "ImageView"; }
|
||||
public:
|
||||
Image(Element *parent, std::string_view src);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,37 +0,0 @@
|
||||
#include "ui_label.h"
|
||||
#include "ui_theme.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Label::Label(Element *parent, LabelStyle label_style) : Element(parent, 0U, "div", true) {
|
||||
switch (label_style) {
|
||||
case LabelStyle::Annotation:
|
||||
set_typography(theme::Typography::LabelXS);
|
||||
break;
|
||||
case LabelStyle::Small:
|
||||
set_typography(theme::Typography::Body);
|
||||
break;
|
||||
case LabelStyle::Normal:
|
||||
set_typography(theme::Typography::LabelMD);
|
||||
break;
|
||||
case LabelStyle::Large:
|
||||
set_typography(theme::Typography::Header3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum class LabelStyle {
|
||||
Annotation,
|
||||
Small,
|
||||
Normal,
|
||||
Large
|
||||
};
|
||||
|
||||
class Label : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Label"; }
|
||||
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
|
||||
@@ -1,272 +0,0 @@
|
||||
#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
|
||||
@@ -1,81 +0,0 @@
|
||||
#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
|
||||
@@ -1,206 +0,0 @@
|
||||
#include "ui_pill_button.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
static constexpr float pill_padding = 16.0f;
|
||||
|
||||
PillButton::PillButton(Element *parent, const std::string &text, const std::string &svg_src, ButtonStyle style, PillButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") {
|
||||
this->style = style;
|
||||
this->size = size;
|
||||
// Borders add width to the button, so this subtracts from the base size to bring it back to the expected size.
|
||||
float float_size_internal = static_cast<float>(size) - (theme::border::width * 2.0f);
|
||||
|
||||
enable_focus();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::Center);
|
||||
set_min_width(float_size_internal);
|
||||
set_width_auto();
|
||||
set_padding_right(pill_padding);
|
||||
set_padding_left(pill_padding);
|
||||
set_height(float_size_internal);
|
||||
set_min_height(float_size_internal);
|
||||
set_max_height(float_size_internal);
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(float_size_internal * 0.5f);
|
||||
set_border_color(theme::color::Transparent);
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_color(theme::color::TextDim);
|
||||
set_tab_index(TabIndex::Auto);
|
||||
set_opacity(1.0f);
|
||||
|
||||
hover_style.set_color(theme::color::Text);
|
||||
focus_style.set_color(theme::color::Text);
|
||||
disabled_style.set_color(theme::color::TextDim);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
disabled_style.set_opacity(0.5f);
|
||||
hover_disabled_style.set_color(theme::color::TextDim);
|
||||
ContextId context = get_current_context();
|
||||
|
||||
bool has_svg = !svg_src.empty();
|
||||
bool has_text = !text.empty();
|
||||
|
||||
if (has_svg) {
|
||||
float icon_size = 0;
|
||||
switch (size) {
|
||||
case PillButtonSize::Mini:
|
||||
icon_size = 16.0f;
|
||||
break;
|
||||
case PillButtonSize::Small:
|
||||
icon_size = 24.0f;
|
||||
break;
|
||||
case PillButtonSize::Medium:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case PillButtonSize::Large:
|
||||
default:
|
||||
icon_size = 32.0f;
|
||||
break;
|
||||
case PillButtonSize::XLarge:
|
||||
icon_size = 40.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
svg = context.create_element<Svg>(this, svg_src);
|
||||
svg->set_width(icon_size);
|
||||
svg->set_image_color(theme::color::TextDim);
|
||||
}
|
||||
|
||||
if (has_text) {
|
||||
auto label = context.create_element<Label>(this, text, LabelStyle::Normal);
|
||||
if (has_svg) {
|
||||
label->set_margin_left(8.0f);
|
||||
}
|
||||
}
|
||||
|
||||
apply_button_style(style);
|
||||
}
|
||||
|
||||
void PillButton::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
for (const auto &function : pressed_callbacks) {
|
||||
function();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
{
|
||||
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||
set_style_enabled(hover_state, hover_active);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(hover_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
if (svg != nullptr && !has_override_text_color) {
|
||||
svg->set_image_color(focus_active ? theme::color::Text : theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PillButton::add_pressed_callback(std::function<void()> callback) {
|
||||
pressed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void PillButton::apply_button_style(ButtonStyle new_style) {
|
||||
style = new_style;
|
||||
switch (style) {
|
||||
case ButtonStyle::Primary: {
|
||||
apply_theme_style(theme::color::Primary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Secondary: {
|
||||
apply_theme_style(theme::color::Secondary);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Tertiary: {
|
||||
apply_theme_style(theme::color::Text);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Success: {
|
||||
apply_theme_style(theme::color::Success);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Warning: {
|
||||
apply_theme_style(theme::color::Warning);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Danger: {
|
||||
apply_theme_style(theme::color::Danger);
|
||||
break;
|
||||
}
|
||||
case ButtonStyle::Basic: {
|
||||
apply_theme_style(theme::color::Text, true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown button style.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PillButton::apply_theme_style(recompui::theme::color color, bool is_basic, bool with_text) {
|
||||
const uint8_t background_opacity = is_basic ? 0 : 13;
|
||||
const uint8_t border_opacity = is_basic ? 0 : 204;
|
||||
const uint8_t background_hover_opacity = 77;
|
||||
const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255;
|
||||
|
||||
has_override_text_color = with_text;
|
||||
|
||||
set_border_color(color, border_opacity);
|
||||
set_background_color(color, background_opacity);
|
||||
hover_style.set_border_color(color, border_hover_opacity);
|
||||
hover_style.set_background_color(color, background_hover_opacity);
|
||||
focus_style.set_border_color(color, border_hover_opacity);
|
||||
focus_style.set_background_color(color, background_hover_opacity);
|
||||
|
||||
if (with_text) {
|
||||
set_color(color);
|
||||
if (svg != nullptr) {
|
||||
svg->set_image_color(color);
|
||||
}
|
||||
}
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_button.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
namespace recompui {
|
||||
enum class PillButtonSize {
|
||||
Mini = 20, // 20x20 (Inline with body text)
|
||||
Small = 32, // 32x32
|
||||
Medium = 48, // 48x48
|
||||
Large = 56, // 56x56
|
||||
Default = PillButtonSize::Large,
|
||||
XLarge = 72, // 72x72
|
||||
};
|
||||
|
||||
class PillButton : public Element {
|
||||
protected:
|
||||
ButtonStyle style = ButtonStyle::Primary;
|
||||
PillButtonSize size = PillButtonSize::Default;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void()>> pressed_callbacks;
|
||||
Svg *svg = nullptr;
|
||||
bool has_override_text_color = false;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "PillButton"; }
|
||||
public:
|
||||
PillButton(Element *parent, const std::string &text, const std::string &svg_src, ButtonStyle style, PillButtonSize size = PillButtonSize::Default);
|
||||
void add_pressed_callback(std::function<void()> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void apply_button_style(ButtonStyle new_style);
|
||||
void apply_theme_style(recompui::theme::color color, bool is_basic = false, bool with_text = false);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,43 +0,0 @@
|
||||
#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
|
||||
@@ -1,22 +0,0 @@
|
||||
#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
|
||||
@@ -1,291 +0,0 @@
|
||||
#include "overloaded.h"
|
||||
#include "ui_radio.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
// RadioOption
|
||||
|
||||
RadioOption::RadioOption(Element *parent, std::string_view name, uint32_t index) : Element(parent, Events(EventType::MouseButton, EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable, EventType::Update), "label", true) {
|
||||
this->index = index;
|
||||
|
||||
enable_focus();
|
||||
set_text(name);
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_typography(theme::Typography::LabelSM);
|
||||
set_border_color(theme::color::Text, 0);
|
||||
set_border_bottom_width(theme::border::width);
|
||||
set_color(theme::color::TextInactive);
|
||||
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) {
|
||||
pressed_callback = callback;
|
||||
}
|
||||
|
||||
void RadioOption::set_focus_callback(std::function<void(bool)> callback) {
|
||||
focus_callback = callback;
|
||||
}
|
||||
|
||||
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::Click:
|
||||
pressed_callback(index);
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active);
|
||||
break;
|
||||
case EventType::Enable: {
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, active);
|
||||
if (active) {
|
||||
queue_update();
|
||||
}
|
||||
if (focus_callback != nullptr) {
|
||||
focus_callback(active);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
if (is_style_enabled(focus_state)) {
|
||||
pulsing_style.set_color(recompui::get_pulse_color(750));
|
||||
apply_styles();
|
||||
queue_update();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Radio
|
||||
|
||||
void Radio::set_index_internal(uint32_t index, bool setup, bool trigger_callbacks) {
|
||||
if (this->index != index || setup) {
|
||||
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;
|
||||
|
||||
if (trigger_callbacks) {
|
||||
for (const auto &function : index_changed_callbacks) {
|
||||
function(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::option_selected(uint32_t index) {
|
||||
set_index_internal(index, false, true);
|
||||
}
|
||||
|
||||
void Radio::set_input_value(const ElementValue& val) {
|
||||
std::visit(overloaded {
|
||||
[this](uint32_t u) { set_index(u); },
|
||||
[this](float f) { set_index(f); },
|
||||
[this](double d) { set_index(d); },
|
||||
[](std::monostate) {}
|
||||
}, val);
|
||||
}
|
||||
|
||||
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();
|
||||
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 (focus_callback != nullptr) {
|
||||
focus_callback(std::get<EventFocus>(e.variant).active);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
if (child_focus_queued) {
|
||||
child_focus_queued = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Radio::~Radio() {
|
||||
|
||||
}
|
||||
|
||||
void Radio::add_option(std::string_view name) {
|
||||
RadioOption *option = get_current_context().create_element<RadioOption>(this, name, uint32_t(options.size()));
|
||||
option->set_pressed_callback([this](uint32_t index){ options[index]->focus(); option_selected(index); });
|
||||
option->set_focus_callback([this](bool active) {
|
||||
if (focus_callback != nullptr) {
|
||||
focus_callback(active);
|
||||
}
|
||||
});
|
||||
options.emplace_back(option);
|
||||
|
||||
// The first option was added, select it.
|
||||
if (options.size() == 1) {
|
||||
set_index_internal(0, true, false);
|
||||
}
|
||||
// At least one other option already existed, so set up navigation.
|
||||
else {
|
||||
options[options.size() - 2]->set_nav(NavDirection::Right, options[options.size() - 1]);
|
||||
options[options.size() - 1]->set_nav(NavDirection::Left, options[options.size() - 2]);
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_index(uint32_t index) {
|
||||
set_index_internal(index, false, false);
|
||||
}
|
||||
|
||||
uint32_t Radio::get_index() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
void Radio::add_index_changed_callback(std::function<void(uint32_t)> callback) {
|
||||
index_changed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Radio::set_focus_callback(std::function<void(bool)> callback) {
|
||||
focus_callback = callback;
|
||||
}
|
||||
|
||||
void Radio::set_nav_auto(NavDirection dir) {
|
||||
Element::set_nav_auto(dir);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav_auto(dir);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav_auto(dir);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav_auto(dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_nav_none(NavDirection dir) {
|
||||
Element::set_nav_none(dir);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav_none(dir);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav_none(dir);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav_none(dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_nav(NavDirection dir, Element* element) {
|
||||
Element::set_nav(dir, element);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav(dir, element);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav(dir, element);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav(dir, element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Radio::set_nav_manual(NavDirection dir, const std::string& target) {
|
||||
Element::set_nav_manual(dir, target);
|
||||
if (!options.empty()) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
case NavDirection::Down:
|
||||
for (Element* e : options) {
|
||||
e->set_nav_manual(dir, target);
|
||||
}
|
||||
break;
|
||||
case NavDirection::Left:
|
||||
options.front()->set_nav_manual(dir, target);
|
||||
break;
|
||||
case NavDirection::Right:
|
||||
options.back()->set_nav_manual(dir, target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_container.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
class RadioOption : public Element {
|
||||
private:
|
||||
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;
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "LabelRadioOption"; }
|
||||
public:
|
||||
RadioOption(Element *parent, std::string_view name, uint32_t index);
|
||||
void set_pressed_callback(std::function<void(uint32_t)> callback);
|
||||
void set_focus_callback(std::function<void(bool)> callback);
|
||||
void set_selected_state(bool enable);
|
||||
};
|
||||
|
||||
class Radio : public Container {
|
||||
private:
|
||||
std::vector<RadioOption *> options;
|
||||
uint32_t index = 0;
|
||||
std::vector<std::function<void(uint32_t)>> index_changed_callbacks;
|
||||
std::function<void(bool)> focus_callback = nullptr;
|
||||
bool child_focus_queued = false;
|
||||
|
||||
void set_index_internal(uint32_t index, bool setup, bool trigger_callbacks);
|
||||
void option_selected(uint32_t index);
|
||||
void set_input_value(const ElementValue& val) override;
|
||||
ElementValue get_element_value() override { return get_index(); }
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "LabelRadio"; }
|
||||
void queue_child_focus() { child_focus_queued = true; queue_update(); }
|
||||
public:
|
||||
Radio(Element *parent);
|
||||
virtual ~Radio();
|
||||
void add_option(std::string_view name);
|
||||
void set_index(uint32_t index);
|
||||
uint32_t get_index() const;
|
||||
void add_index_changed_callback(std::function<void(uint32_t)> callback);
|
||||
void set_focus_callback(std::function<void(bool)> callback);
|
||||
size_t num_options() const { return options.size(); }
|
||||
RadioOption* get_option_element(size_t option_index) { return options[option_index]; }
|
||||
RadioOption* get_current_option_element() { return options.empty() ? nullptr : options[index]; }
|
||||
void set_nav_auto(NavDirection dir) override;
|
||||
void set_nav_none(NavDirection dir) override;
|
||||
void set_nav(NavDirection dir, Element* element) override;
|
||||
void set_nav_manual(NavDirection dir, const std::string& target) override;
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "ui_scroll_container.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ScrollContainer::ScrollContainer(Element *parent, ScrollDirection direction) : Element(parent) {
|
||||
set_flex(1.0f, 1.0f, 100.0f);
|
||||
set_width(100.0f, Unit::Percent);
|
||||
set_height(100.0f, Unit::Percent);
|
||||
|
||||
switch (direction) {
|
||||
case ScrollDirection::Horizontal:
|
||||
set_max_width(100.0f, Unit::Percent);
|
||||
set_overflow_x(Overflow::Auto);
|
||||
break;
|
||||
case ScrollDirection::Vertical:
|
||||
set_max_height(100.0f, Unit::Percent);
|
||||
set_overflow_y(Overflow::Auto);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown scroll direction.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum class ScrollDirection {
|
||||
Horizontal,
|
||||
Vertical
|
||||
};
|
||||
|
||||
class ScrollContainer : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "ScrollContainer"; }
|
||||
public:
|
||||
ScrollContainer(Element *parent, ScrollDirection direction);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,296 +0,0 @@
|
||||
#include "ui_select.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
Option::Option(Element *parent, const SelectOption &option) :
|
||||
Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "option", true),
|
||||
option(option)
|
||||
{
|
||||
set_text(option.text);
|
||||
set_input_text(option.value);
|
||||
set_color(theme::color::Primary);
|
||||
|
||||
set_padding(12.0f);
|
||||
set_height_auto();
|
||||
set_width_auto();
|
||||
set_overflow(Overflow::Hidden);
|
||||
|
||||
set_min_width(128.0f);
|
||||
|
||||
set_font_size(18.0f);
|
||||
set_letter_spacing(2.52f);
|
||||
set_line_height(18.0f);
|
||||
set_font_weight(400);
|
||||
|
||||
set_background_color(theme::color::Transparent);
|
||||
set_cursor(Cursor::Pointer);
|
||||
|
||||
hover_style.set_color(theme::color::TextActive);
|
||||
hover_style.set_background_color(theme::color::White, 77);
|
||||
|
||||
focus_style.set_color(theme::color::TextActive);
|
||||
focus_style.set_background_color(theme::color::White, 77);
|
||||
|
||||
disabled_style.set_color(theme::color::TextDim, 128);
|
||||
disabled_style.set_background_color(theme::color::Transparent);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
}
|
||||
|
||||
void Option::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
break;
|
||||
case EventType::Hover:
|
||||
printf("Option hovered: %s\n", option.text.c_str());
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus:
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
constexpr std::string_view select_element_selectbox = "selectbox";
|
||||
constexpr std::string_view select_element_selectarrow = "selectarrow";
|
||||
constexpr std::string_view select_element_selectvalue = "selectvalue";
|
||||
|
||||
constexpr float select_element_height = 48.0f;
|
||||
constexpr float select_element_min_width = 128.0f + 64.0f; // 128px for the select box, 64px for the arrow
|
||||
constexpr float select_element_padding = 16.0f;
|
||||
constexpr float select_element_caret_size = 24.0f;
|
||||
|
||||
Select::Select(
|
||||
Element *parent, std::vector<SelectOption> options, std::string selected_option_value
|
||||
) :
|
||||
Element(
|
||||
parent,
|
||||
Events(EventType::Text, EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus),
|
||||
"select",
|
||||
false),
|
||||
selected_option_value(selected_option_value)
|
||||
{
|
||||
this->options = options;
|
||||
|
||||
ContextId context = get_current_context();
|
||||
wrapper = context.create_element<Element>(parent, 0, "div", false);
|
||||
set_parent(wrapper);
|
||||
wrapper->set_height_auto();
|
||||
wrapper->set_width(100.0f, Unit::Percent);
|
||||
wrapper->set_position(Position::Relative);
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_align_items(AlignItems::Center);
|
||||
set_justify_content(JustifyContent::FlexStart);
|
||||
set_gap(16.0f);
|
||||
set_text_align(TextAlign::Left);
|
||||
set_position(Position::Relative);
|
||||
set_padding_right(select_element_padding);
|
||||
set_padding_left(select_element_padding);
|
||||
set_height(select_element_height);
|
||||
set_width_auto();
|
||||
set_overflow(Overflow::Hidden);
|
||||
|
||||
set_min_width(select_element_min_width);
|
||||
|
||||
set_border_width(theme::border::width);
|
||||
set_border_radius(theme::border::radius_md);
|
||||
set_border_color(theme::color::Border, 204);
|
||||
|
||||
set_font_size(20.0f);
|
||||
set_letter_spacing(0.0f);
|
||||
set_line_height(20.0f);
|
||||
set_font_weight(400);
|
||||
set_font_style(FontStyle::Normal);
|
||||
|
||||
set_focusable(true);
|
||||
set_color(theme::color::Text);
|
||||
set_background_color(theme::color::Transparent);
|
||||
set_cursor(Cursor::Pointer);
|
||||
|
||||
set_nav_auto(NavDirection::Up);
|
||||
set_nav_auto(NavDirection::Right);
|
||||
set_nav_auto(NavDirection::Down);
|
||||
set_nav_auto(NavDirection::Left);
|
||||
|
||||
hover_style.set_color(theme::color::TextActive);
|
||||
hover_style.set_background_color(theme::color::White, 77);
|
||||
hover_style.set_border_color(theme::color::Border, 255);
|
||||
|
||||
focus_style.set_color(theme::color::TextActive);
|
||||
focus_style.set_background_color(theme::color::White, 77);
|
||||
focus_style.set_border_color(theme::color::Border, 255);
|
||||
|
||||
disabled_style.set_color(theme::color::TextDim, 128);
|
||||
disabled_style.set_background_color(theme::color::Transparent);
|
||||
disabled_style.set_border_color(theme::color::Border, 77);
|
||||
disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
hover_disabled_style.set_color(theme::color::TextDim, 128);
|
||||
hover_disabled_style.set_background_color(theme::color::Transparent);
|
||||
hover_disabled_style.set_border_color(theme::color::Border, 77);
|
||||
hover_disabled_style.set_cursor(Cursor::None);
|
||||
|
||||
Element selectbox_element = get_element_with_tag_name(select_element_selectbox);
|
||||
selectbox_element.set_margin_top(4.0f);
|
||||
selectbox_element.set_width_auto();
|
||||
selectbox_element.set_padding_top(8.0f);
|
||||
selectbox_element.set_padding_bottom(8.0f);
|
||||
selectbox_element.set_background_color(theme::color::Background3);
|
||||
selectbox_element.set_border_width(theme::border::width);
|
||||
selectbox_element.set_border_radius(theme::border::radius_sm);
|
||||
selectbox_element.set_border_color(theme::color::BorderSoft);
|
||||
|
||||
Element selectvalue_element = get_element_with_tag_name(select_element_selectvalue);
|
||||
selectvalue_element.set_display(Display::Block);
|
||||
selectvalue_element.set_position(Position::Absolute);
|
||||
selectvalue_element.set_top(50.0f, recompui::Unit::Percent);
|
||||
selectvalue_element.set_left(select_element_padding);
|
||||
selectvalue_element.set_width_auto();
|
||||
selectvalue_element.set_height_auto();
|
||||
selectvalue_element.set_translate_2D(0.0f, -50.0f, recompui::Unit::Percent);
|
||||
|
||||
Element selectarrow_element = get_element_with_tag_name(select_element_selectarrow);
|
||||
selectarrow_element.set_display(Display::None);
|
||||
|
||||
add_option_elements();
|
||||
|
||||
arrow = context.create_element<Svg>(
|
||||
wrapper,
|
||||
"icons/Caret.svg"
|
||||
);
|
||||
|
||||
arrow->set_display(Display::Block);
|
||||
|
||||
arrow->set_position(Position::Absolute);
|
||||
arrow->set_top(50.0f, recompui::Unit::Percent);
|
||||
arrow->set_right(select_element_padding);
|
||||
arrow->set_translate_2D(0, -50.0f, recompui::Unit::Percent);
|
||||
|
||||
arrow->set_width(select_element_caret_size);
|
||||
arrow->set_height(select_element_caret_size);
|
||||
arrow->set_color(theme::color::TextDim);
|
||||
// makes clicking on the arrow pass click interactions through to whats beneath it
|
||||
arrow->set_pointer_events(PointerEvents::None);
|
||||
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&hover_disabled_style, { hover_state, disabled_state });
|
||||
}
|
||||
|
||||
void Select::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
break;
|
||||
case EventType::Hover:
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
set_style_enabled(disabled_state, !enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Focus: {
|
||||
bool focus_active = std::get<EventFocus>(e.variant).active;
|
||||
set_style_enabled(focus_state, focus_active);
|
||||
break;
|
||||
}
|
||||
case EventType::Text:
|
||||
{
|
||||
const std::string& opt_value = std::get<EventText>(e.variant).text;
|
||||
|
||||
int index = -1;
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
if (options[i].value == opt_value) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
selected_option_index = index;
|
||||
for (const auto &callback : change_callbacks) {
|
||||
callback(options[index], selected_option_index);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update: {
|
||||
Element selectvalue_element = get_element_with_tag_name(select_element_selectvalue);
|
||||
bool is_now_open = selectvalue_element.is_pseudo_class_set("checked");
|
||||
if (is_now_open != is_open) {
|
||||
is_open = is_now_open;
|
||||
if (is_open) {
|
||||
arrow->set_rotation(180.0f);
|
||||
} else {
|
||||
arrow->set_rotation(0.0f);
|
||||
}
|
||||
}
|
||||
queue_update();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Select::add_change_callback(std::function<void(SelectOption& option, int option_index)> callback) {
|
||||
change_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Select::add_option_elements() {
|
||||
clear_children();
|
||||
option_elements.clear();
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
auto &option = options[i];
|
||||
Option *option_element = context.create_element<Option>(this, option);
|
||||
option_elements.push_back(option_element);
|
||||
|
||||
if (!selected_option_value.empty() && option.value == selected_option_value) {
|
||||
set_selection(selected_option_value);
|
||||
selected_option_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_svg.h"
|
||||
|
||||
|
||||
namespace recompui {
|
||||
enum class SelectOptionStyle {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Success,
|
||||
Warning,
|
||||
Danger,
|
||||
Default = Tertiary,
|
||||
};
|
||||
|
||||
struct SelectOption {
|
||||
std::string text;
|
||||
std::string value;
|
||||
SelectOptionStyle style = SelectOptionStyle::Default;
|
||||
|
||||
SelectOption(const std::string &text, const std::string &value, SelectOptionStyle style = SelectOptionStyle::Default)
|
||||
: text(text), value(value), style(style) {}
|
||||
};
|
||||
|
||||
class Option : public Element {
|
||||
protected:
|
||||
SelectOption option;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Option"; }
|
||||
public:
|
||||
Option(Element *parent, const SelectOption &option);
|
||||
};
|
||||
|
||||
class Select : public Element {
|
||||
protected:
|
||||
int selected_option_index = -1;
|
||||
std::string selected_option_value;
|
||||
Element *wrapper = nullptr;
|
||||
Svg *arrow = nullptr;
|
||||
std::vector<SelectOption> options;
|
||||
std::vector<Option*> option_elements;
|
||||
bool is_open = false;
|
||||
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style disabled_style;
|
||||
Style hover_disabled_style;
|
||||
std::list<std::function<void(SelectOption& option, int option_index)>> change_callbacks;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Select"; }
|
||||
public:
|
||||
Select(
|
||||
Element *parent,
|
||||
std::vector<SelectOption> options = {},
|
||||
std::string selected_option_value = ""
|
||||
);
|
||||
void add_change_callback(std::function<void(SelectOption& option, int option_index)> callback);
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
Style* get_disabled_style() { return &disabled_style; }
|
||||
Style* get_hover_disabled_style() { return &hover_disabled_style; }
|
||||
void set_selection(std::string_view option_value) {
|
||||
select_set_selection(option_value);
|
||||
}
|
||||
private:
|
||||
void add_option_elements();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,252 +0,0 @@
|
||||
#include "overloaded.h"
|
||||
#include "ui_slider.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <charconv>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
void Slider::set_value_internal(double v, bool setup, bool trigger_callbacks) {
|
||||
if (step_value != 0.0) {
|
||||
v = std::lround(v / step_value) * step_value;
|
||||
}
|
||||
|
||||
if (value != v || setup) {
|
||||
value = v;
|
||||
update_circle_position();
|
||||
update_label_text();
|
||||
|
||||
if (trigger_callbacks) {
|
||||
for (auto callback : value_changed_callbacks) {
|
||||
callback(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Slider::bar_pressed(float x, float) {
|
||||
update_value_from_mouse(x);
|
||||
}
|
||||
|
||||
void Slider::bar_dragged(float x, float, DragPhase) {
|
||||
update_value_from_mouse(x);
|
||||
}
|
||||
|
||||
void Slider::circle_dragged(float x, float, DragPhase) {
|
||||
update_value_from_mouse(x);
|
||||
}
|
||||
|
||||
void Slider::update_value_from_mouse(float x) {
|
||||
double left = slider_element->get_absolute_left();
|
||||
double width = slider_element->get_client_width();
|
||||
double ratio = std::clamp((x - left) / width, 0.0, 1.0);
|
||||
set_value_internal(min_value + ratio * (max_value - min_value), false, true);
|
||||
}
|
||||
|
||||
void Slider::update_circle_position() {
|
||||
double ratio = std::clamp((value - min_value) / (max_value - min_value), 0.0, 1.0);
|
||||
circle_element->set_left(ratio * 100.0, Unit::Percent);
|
||||
}
|
||||
|
||||
void Slider::update_label_text() {
|
||||
char text_buffer[32];
|
||||
|
||||
if (type == SliderType::Double) {
|
||||
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 {
|
||||
std::snprintf(text_buffer, sizeof(text_buffer), "%d", static_cast<int>(value));
|
||||
}
|
||||
|
||||
value_label->set_text(text_buffer);
|
||||
}
|
||||
|
||||
void Slider::set_input_value(const ElementValue& val) {
|
||||
std::visit(overloaded {
|
||||
[this](uint32_t u) { set_value(u); },
|
||||
[this](float f) { set_value(f); },
|
||||
[this](double d) { set_value(d); },
|
||||
[](std::monostate) {}
|
||||
}, val);
|
||||
}
|
||||
|
||||
void Slider::process_event(const Event& e) {
|
||||
switch (e.type) {
|
||||
case EventType::Focus:
|
||||
{
|
||||
bool active = std::get<EventFocus>(e.variant).active;
|
||||
circle_element->set_style_enabled(focus_state, active);
|
||||
if (active) {
|
||||
queue_update();
|
||||
}
|
||||
if (focus_callback != nullptr) {
|
||||
focus_callback(active);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
if (is_enabled()) {
|
||||
if (circle_element->is_style_enabled(focus_state)) {
|
||||
circle_element->set_background_color(recompui::get_pulse_color(750));
|
||||
queue_update();
|
||||
}
|
||||
else {
|
||||
circle_element->set_background_color(theme::color::TextDim);
|
||||
}
|
||||
}
|
||||
else {
|
||||
circle_element->set_background_color(theme::color::BW25);
|
||||
}
|
||||
break;
|
||||
case EventType::Navigate:
|
||||
{
|
||||
NavDirection dir = std::get<EventNavigate>(e.variant).direction;
|
||||
if (dir == NavDirection::Left) {
|
||||
do_step(false);
|
||||
}
|
||||
else if (dir == NavDirection::Right) {
|
||||
do_step(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType::Enable:
|
||||
{
|
||||
bool enable_active = std::get<EventEnable>(e.variant).active;
|
||||
// circle_element->set_enabled(enable_active);
|
||||
// slider_element->set_enabled(enable_active);
|
||||
if (enable_active) {
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_focusable(true);
|
||||
circle_element->set_background_color(theme::color::TextDim);
|
||||
}
|
||||
else {
|
||||
set_cursor(Cursor::None);
|
||||
set_focusable(false);
|
||||
circle_element->set_background_color(theme::color::BW25);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Slider::Slider(Element *parent, SliderType type) : Element(parent, Events(EventType::Focus, EventType::Update, EventType::Navigate, EventType::Enable)) {
|
||||
this->type = type;
|
||||
|
||||
set_cursor(Cursor::Pointer);
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Row);
|
||||
set_text_align(TextAlign::Left);
|
||||
set_min_width(120.0f);
|
||||
|
||||
enable_focus();
|
||||
set_nav_none(NavDirection::Left);
|
||||
set_nav_none(NavDirection::Right);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
value_label = context.create_element<Label>(this, "0", LabelStyle::Small);
|
||||
value_label->set_margin_right(20.0f);
|
||||
value_label->set_min_width(60.0f);
|
||||
value_label->set_max_width(60.0f);
|
||||
|
||||
slider_element = context.create_element<Clickable>(this, true);
|
||||
slider_element->set_flex(1.0f, 0.0f);
|
||||
slider_element->add_pressed_callback([this](float x, float y){ bar_pressed(x, y); focus(); });
|
||||
slider_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); });
|
||||
|
||||
{
|
||||
bar_element = context.create_element<Clickable>(slider_element, true);
|
||||
bar_element->set_width(100.0f, Unit::Percent);
|
||||
bar_element->set_height(2.0f);
|
||||
bar_element->set_margin_top(8.0f);
|
||||
bar_element->set_background_color(theme::color::WhiteA20);
|
||||
bar_element->add_pressed_callback([this](float x, float y){ bar_pressed(x, y); focus(); });
|
||||
bar_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); focus(); });
|
||||
|
||||
circle_element = context.create_element<Clickable>(bar_element, true);
|
||||
circle_element->set_position(Position::Relative);
|
||||
circle_element->set_width(16.0f);
|
||||
circle_element->set_height(16.0f);
|
||||
circle_element->set_margin_top(-7.0f);
|
||||
circle_element->set_margin_right(-8.0f);
|
||||
circle_element->set_margin_left(-8.0f);
|
||||
circle_element->set_background_color(theme::color::TextDim);
|
||||
circle_element->set_border_radius(theme::border::radius_sm);
|
||||
circle_element->add_pressed_callback([this](float, float){ focus(); });
|
||||
circle_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ circle_dragged(x, y, phase); focus(); });
|
||||
circle_element->set_cursor(Cursor::Pointer);
|
||||
}
|
||||
|
||||
set_value_internal(value, true, false);
|
||||
}
|
||||
|
||||
Slider::~Slider() {
|
||||
|
||||
}
|
||||
|
||||
void Slider::set_value(double v) {
|
||||
set_value_internal(v, false, false);
|
||||
}
|
||||
|
||||
double Slider::get_value() const {
|
||||
return value;
|
||||
}
|
||||
void Slider::set_min_value(double v) {
|
||||
min_value = v;
|
||||
}
|
||||
|
||||
double Slider::get_min_value() const {
|
||||
return min_value;
|
||||
}
|
||||
|
||||
void Slider::set_max_value(double v) {
|
||||
max_value = v;
|
||||
}
|
||||
|
||||
double Slider::get_max_value() const {
|
||||
return max_value;
|
||||
}
|
||||
|
||||
void Slider::set_step_value(double v) {
|
||||
step_value = v;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Slider::set_focus_callback(std::function<void(bool)> callback) {
|
||||
focus_callback = callback;
|
||||
}
|
||||
|
||||
void Slider::do_step(bool increment) {
|
||||
double new_value = value;
|
||||
if (increment) {
|
||||
new_value += step_value;
|
||||
}
|
||||
else {
|
||||
new_value -= step_value;
|
||||
}
|
||||
new_value = std::clamp(new_value, min_value, max_value);
|
||||
if (new_value != value) {
|
||||
set_value_internal(new_value, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_clickable.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
enum SliderType {
|
||||
Double,
|
||||
Percent,
|
||||
Integer
|
||||
};
|
||||
|
||||
class Slider : public Element {
|
||||
private:
|
||||
SliderType type = SliderType::Percent;
|
||||
Label *value_label = nullptr;
|
||||
Clickable *slider_element = nullptr;
|
||||
Clickable *bar_element = nullptr;
|
||||
Clickable *circle_element = nullptr;
|
||||
double value = 50.0;
|
||||
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;
|
||||
|
||||
void set_value_internal(double v, bool setup, bool trigger_callbacks);
|
||||
void bar_pressed(float x, float y);
|
||||
void bar_dragged(float x, float y, DragPhase phase);
|
||||
void circle_dragged(float x, float y, DragPhase phase);
|
||||
void update_value_from_mouse(float x);
|
||||
void update_circle_position();
|
||||
void update_label_text();
|
||||
void set_input_value(const ElementValue& val) override;
|
||||
ElementValue get_element_value() override { return get_value(); }
|
||||
|
||||
protected:
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "Slider"; }
|
||||
|
||||
public:
|
||||
Slider(Element *parent, SliderType type);
|
||||
virtual ~Slider();
|
||||
void set_value(double v);
|
||||
double get_value() const;
|
||||
void set_min_value(double v);
|
||||
double get_min_value() const;
|
||||
void set_max_value(double v);
|
||||
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);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,15 +0,0 @@
|
||||
#include "ui_span.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Span::Span(Element *parent) : Element(parent, 0, "span", true) {
|
||||
set_font_style(FontStyle::Normal);
|
||||
}
|
||||
|
||||
Span::Span(Element *parent, const std::string &text) : Span(parent) {
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
#include "ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Span : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Span"; }
|
||||
public:
|
||||
Span(Element *parent);
|
||||
Span(Element *parent, const std::string &text);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,747 +0,0 @@
|
||||
#include "ui_style.h"
|
||||
#include "ui_element.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
static Rml::Unit to_rml(Unit unit) {
|
||||
switch (unit) {
|
||||
case Unit::Px:
|
||||
return Rml::Unit::PX;
|
||||
case Unit::Dp:
|
||||
return Rml::Unit::DP;
|
||||
case Unit::Percent:
|
||||
return Rml::Unit::PERCENT;
|
||||
default:
|
||||
return Rml::Unit::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::AlignItems to_rml(AlignItems align_items) {
|
||||
switch (align_items) {
|
||||
case AlignItems::FlexStart:
|
||||
return Rml::Style::AlignItems::FlexStart;
|
||||
case AlignItems::FlexEnd:
|
||||
return Rml::Style::AlignItems::FlexEnd;
|
||||
case AlignItems::Center:
|
||||
return Rml::Style::AlignItems::Center;
|
||||
case AlignItems::Baseline:
|
||||
return Rml::Style::AlignItems::Baseline;
|
||||
case AlignItems::Stretch:
|
||||
return Rml::Style::AlignItems::Stretch;
|
||||
default:
|
||||
assert(false && "Unknown align items.");
|
||||
return Rml::Style::AlignItems::FlexStart;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::Overflow to_rml(Overflow overflow) {
|
||||
switch (overflow) {
|
||||
case Overflow::Visible:
|
||||
return Rml::Style::Overflow::Visible;
|
||||
case Overflow::Hidden:
|
||||
return Rml::Style::Overflow::Hidden;
|
||||
case Overflow::Auto:
|
||||
return Rml::Style::Overflow::Auto;
|
||||
case Overflow::Scroll:
|
||||
return Rml::Style::Overflow::Scroll;
|
||||
default:
|
||||
assert(false && "Unknown overflow.");
|
||||
return Rml::Style::Overflow::Visible;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::TextAlign to_rml(TextAlign text_align) {
|
||||
switch (text_align) {
|
||||
case TextAlign::Left:
|
||||
return Rml::Style::TextAlign::Left;
|
||||
case TextAlign::Right:
|
||||
return Rml::Style::TextAlign::Right;
|
||||
case TextAlign::Center:
|
||||
return Rml::Style::TextAlign::Center;
|
||||
case TextAlign::Justify:
|
||||
return Rml::Style::TextAlign::Justify;
|
||||
default:
|
||||
assert(false && "Unknown text align.");
|
||||
return Rml::Style::TextAlign::Left;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::TextTransform to_rml(TextTransform text_transform) {
|
||||
switch (text_transform) {
|
||||
case TextTransform::None:
|
||||
return Rml::Style::TextTransform::None;
|
||||
case TextTransform::Capitalize:
|
||||
return Rml::Style::TextTransform::Capitalize;
|
||||
case TextTransform::Uppercase:
|
||||
return Rml::Style::TextTransform::Uppercase;
|
||||
case TextTransform::Lowercase:
|
||||
return Rml::Style::TextTransform::Lowercase;
|
||||
default:
|
||||
assert(false && "Unknown text transform.");
|
||||
return Rml::Style::TextTransform::None;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::Drag to_rml(Drag drag) {
|
||||
switch (drag) {
|
||||
case Drag::None:
|
||||
return Rml::Style::Drag::None;
|
||||
case Drag::Drag:
|
||||
return Rml::Style::Drag::Drag;
|
||||
case Drag::DragDrop:
|
||||
return Rml::Style::Drag::DragDrop;
|
||||
case Drag::Block:
|
||||
return Rml::Style::Drag::Block;
|
||||
case Drag::Clone:
|
||||
return Rml::Style::Drag::Clone;
|
||||
default:
|
||||
assert(false && "Unknown drag.");
|
||||
return Rml::Style::Drag::None;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::TabIndex to_rml(TabIndex tab_index) {
|
||||
switch (tab_index) {
|
||||
case TabIndex::None:
|
||||
return Rml::Style::TabIndex::None;
|
||||
case TabIndex::Auto:
|
||||
return Rml::Style::TabIndex::Auto;
|
||||
default:
|
||||
assert(false && "Unknown tab index.");
|
||||
return Rml::Style::TabIndex::None;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::Display to_rml(Display display) {
|
||||
switch (display) {
|
||||
case Display::None:
|
||||
return Rml::Style::Display::None;
|
||||
case Display::Block:
|
||||
return Rml::Style::Display::Block;
|
||||
case Display::Inline:
|
||||
return Rml::Style::Display::Inline;
|
||||
case Display::InlineBlock:
|
||||
return Rml::Style::Display::InlineBlock;
|
||||
case Display::FlowRoot:
|
||||
return Rml::Style::Display::FlowRoot;
|
||||
case Display::Flex:
|
||||
return Rml::Style::Display::Flex;
|
||||
case Display::InlineFlex:
|
||||
return Rml::Style::Display::InlineFlex;
|
||||
case Display::Table:
|
||||
return Rml::Style::Display::Table;
|
||||
case Display::InlineTable:
|
||||
return Rml::Style::Display::InlineTable;
|
||||
case Display::TableRow:
|
||||
return Rml::Style::Display::TableRow;
|
||||
case Display::TableRowGroup:
|
||||
return Rml::Style::Display::TableRowGroup;
|
||||
case Display::TableColumn:
|
||||
return Rml::Style::Display::TableColumn;
|
||||
case Display::TableColumnGroup:
|
||||
return Rml::Style::Display::TableColumnGroup;
|
||||
case Display::TableCell:
|
||||
return Rml::Style::Display::TableCell;
|
||||
default:
|
||||
assert(false && "Unknown display.");
|
||||
return Rml::Style::Display::Block;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::Style::JustifyContent to_rml(JustifyContent justify_content) {
|
||||
switch (justify_content) {
|
||||
case JustifyContent::FlexStart:
|
||||
return Rml::Style::JustifyContent::FlexStart;
|
||||
case JustifyContent::FlexEnd:
|
||||
return Rml::Style::JustifyContent::FlexEnd;
|
||||
case JustifyContent::Center:
|
||||
return Rml::Style::JustifyContent::Center;
|
||||
case JustifyContent::SpaceBetween:
|
||||
return Rml::Style::JustifyContent::SpaceBetween;
|
||||
case JustifyContent::SpaceAround:
|
||||
return Rml::Style::JustifyContent::SpaceAround;
|
||||
case JustifyContent::SpaceEvenly:
|
||||
return Rml::Style::JustifyContent::SpaceEvenly;
|
||||
default:
|
||||
assert(false && "Unknown justify content.");
|
||||
return Rml::Style::JustifyContent::FlexStart;
|
||||
}
|
||||
}
|
||||
|
||||
static Rml::PropertyId nav_to_property(NavDirection dir) {
|
||||
switch (dir) {
|
||||
case NavDirection::Up:
|
||||
return Rml::PropertyId::NavUp;
|
||||
case NavDirection::Right:
|
||||
return Rml::PropertyId::NavRight;
|
||||
case NavDirection::Down:
|
||||
return Rml::PropertyId::NavDown;
|
||||
case NavDirection::Left:
|
||||
return Rml::PropertyId::NavLeft;
|
||||
default:
|
||||
assert(false && "Unknown nav direction.");
|
||||
return Rml::PropertyId::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_property(Rml::PropertyId property_id, const Rml::Property &property) {
|
||||
property_map[property_id] = property;
|
||||
}
|
||||
|
||||
Style::Style() {
|
||||
|
||||
}
|
||||
|
||||
Style::~Style() {
|
||||
|
||||
}
|
||||
|
||||
void Style::set_visibility(Visibility visibility) {
|
||||
switch (visibility) {
|
||||
case Visibility::Visible:
|
||||
set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Visible);
|
||||
break;
|
||||
case Visibility::Hidden:
|
||||
set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Hidden);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_position(Position position) {
|
||||
switch (position) {
|
||||
case Position::Absolute:
|
||||
set_property(Rml::PropertyId::Position, Rml::Style::Position::Absolute);
|
||||
break;
|
||||
case Position::Relative:
|
||||
set_property(Rml::PropertyId::Position, Rml::Style::Position::Relative);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown position.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_left(float left, Unit unit) {
|
||||
set_property(Rml::PropertyId::Left, Rml::Property(left, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_top(float top, Unit unit) {
|
||||
set_property(Rml::PropertyId::Top, Rml::Property(top, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_right(float right, Unit unit) {
|
||||
set_property(Rml::PropertyId::Right, Rml::Property(right, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_bottom(float bottom, Unit unit) {
|
||||
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)));
|
||||
}
|
||||
|
||||
void Style::set_width_auto() {
|
||||
set_property(Rml::PropertyId::Width, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::Height, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_height_auto() {
|
||||
set_property(Rml::PropertyId::Height, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_min_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::MinWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_min_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::MinHeight, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_max_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::MaxWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_max_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::MaxHeight, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_left(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingLeft, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_top(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingTop, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_right(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingRight, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_padding_bottom(float padding, Unit unit) {
|
||||
set_property(Rml::PropertyId::PaddingBottom, Rml::Property(padding, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)));
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_left(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_top(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_right(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_bottom(float margin, Unit unit) {
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(margin, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_margin_auto() {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_left_auto() {
|
||||
set_property(Rml::PropertyId::MarginLeft, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_top_auto() {
|
||||
set_property(Rml::PropertyId::MarginTop, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_right_auto() {
|
||||
set_property(Rml::PropertyId::MarginRight, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_margin_bottom_auto() {
|
||||
set_property(Rml::PropertyId::MarginBottom, Rml::Property(Rml::Style::Margin::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_border_width(float width, Unit unit) {
|
||||
Rml::Property property(width, to_rml(unit));
|
||||
set_property(Rml::PropertyId::BorderTopWidth, property);
|
||||
set_property(Rml::PropertyId::BorderBottomWidth, property);
|
||||
set_property(Rml::PropertyId::BorderLeftWidth, property);
|
||||
set_property(Rml::PropertyId::BorderRightWidth, property);
|
||||
}
|
||||
|
||||
void Style::set_border_left_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderLeftWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_top_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderTopWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_right_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderRightWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_width(float width, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderBottomWidth, Rml::Property(width, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_radius(float radius, Unit unit) {
|
||||
Rml::Property property(radius, to_rml(unit));
|
||||
set_property(Rml::PropertyId::BorderTopLeftRadius, property);
|
||||
set_property(Rml::PropertyId::BorderTopRightRadius, property);
|
||||
set_property(Rml::PropertyId::BorderBottomLeftRadius, property);
|
||||
set_property(Rml::PropertyId::BorderBottomRightRadius, property);
|
||||
}
|
||||
|
||||
void Style::set_border_top_left_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderTopLeftRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_top_right_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderTopRightRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_left_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderBottomLeftRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_right_radius(float radius, Unit unit) {
|
||||
set_property(Rml::PropertyId::BorderBottomRightRadius, Rml::Property(radius, to_rml(unit)));
|
||||
}
|
||||
|
||||
static Color get_theme_color_with_opacity(theme::color color, int opacity) {
|
||||
Color theme_color = get_theme_color(color);
|
||||
if (opacity == recompui::ThemeDefaultOpacity) {
|
||||
opacity = theme_color.a; // Use the existing opacity if not specified.
|
||||
}
|
||||
theme_color.a = opacity;
|
||||
return theme_color;
|
||||
}
|
||||
|
||||
void Style::set_background_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BackgroundColor, property);
|
||||
}
|
||||
|
||||
void Style::set_background_color(recompui::theme::color color, int opacity) {
|
||||
set_background_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_border_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderTopColor, property);
|
||||
set_property(Rml::PropertyId::BorderBottomColor, property);
|
||||
set_property(Rml::PropertyId::BorderLeftColor, property);
|
||||
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_color(recompui::theme::color color, int opacity) {
|
||||
set_border_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_border_left_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderLeftColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_left_color(recompui::theme::color color, int opacity) {
|
||||
set_border_left_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_border_top_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderTopColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_top_color(recompui::theme::color color, int opacity) {
|
||||
set_border_top_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_border_right_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderRightColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_right_color(recompui::theme::color color, int opacity) {
|
||||
set_border_right_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::BorderBottomColor, property);
|
||||
}
|
||||
|
||||
void Style::set_border_bottom_color(recompui::theme::color color, int opacity) {
|
||||
set_border_bottom_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::Color, property);
|
||||
}
|
||||
|
||||
void Style::set_color(recompui::theme::color color, int opacity) {
|
||||
set_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_image_color(const Color &color) {
|
||||
Rml::Property property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
set_property(Rml::PropertyId::ImageColor, property);
|
||||
}
|
||||
|
||||
void Style::set_image_color(recompui::theme::color color, int opacity) {
|
||||
set_image_color(get_theme_color_with_opacity(color, opacity));
|
||||
}
|
||||
|
||||
void Style::set_cursor(Cursor cursor) {
|
||||
switch (cursor) {
|
||||
case Cursor::None:
|
||||
set_property(Rml::PropertyId::Cursor, Rml::Property("", Rml::Unit::STRING));
|
||||
break;
|
||||
case Cursor::Pointer:
|
||||
set_property(Rml::PropertyId::Cursor, Rml::Property("pointer", Rml::Unit::STRING));
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown cursor.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_opacity(float opacity) {
|
||||
set_property(Rml::PropertyId::Opacity, Rml::Property(opacity, Rml::Unit::NUMBER));
|
||||
}
|
||||
|
||||
void Style::set_display(Display display) {
|
||||
set_property(Rml::PropertyId::Display, to_rml(display));
|
||||
}
|
||||
|
||||
void Style::set_justify_content(JustifyContent justify_content) {
|
||||
set_property(Rml::PropertyId::JustifyContent, to_rml(justify_content));
|
||||
}
|
||||
|
||||
void Style::set_flex_grow(float grow) {
|
||||
set_property(Rml::PropertyId::FlexGrow, Rml::Property(grow, Rml::Unit::NUMBER));
|
||||
}
|
||||
|
||||
void Style::set_flex_shrink(float shrink) {
|
||||
set_property(Rml::PropertyId::FlexShrink, Rml::Property(shrink, Rml::Unit::NUMBER));
|
||||
}
|
||||
|
||||
void Style::set_flex_basis_auto() {
|
||||
set_property(Rml::PropertyId::FlexBasis, Rml::Property(Rml::Style::FlexBasis::Type::Auto, Rml::Unit::KEYWORD));
|
||||
}
|
||||
|
||||
void Style::set_flex_basis(float basis, Unit unit) {
|
||||
set_property(Rml::PropertyId::FlexBasis, Rml::Property(basis, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_flex(float grow, float shrink) {
|
||||
set_flex_grow(grow);
|
||||
set_flex_shrink(shrink);
|
||||
set_flex_basis_auto();
|
||||
}
|
||||
|
||||
void Style::set_flex(float grow, float shrink, float basis, Unit basis_unit) {
|
||||
set_flex_grow(grow);
|
||||
set_flex_shrink(shrink);
|
||||
set_flex_basis(basis, basis_unit);
|
||||
}
|
||||
|
||||
void Style::set_flex_direction(FlexDirection flex_direction) {
|
||||
switch (flex_direction) {
|
||||
case FlexDirection::Row:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row);
|
||||
break;
|
||||
case FlexDirection::Column:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column);
|
||||
break;
|
||||
case FlexDirection::RowReverse:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::RowReverse);
|
||||
break;
|
||||
case FlexDirection::ColumnReverse:
|
||||
set_property(Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::ColumnReverse);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown flex direction.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_flex_wrap(FlexWrap flex_wrap) {
|
||||
switch (flex_wrap) {
|
||||
case FlexWrap::NoWrap:
|
||||
set_property(Rml::PropertyId::FlexWrap, Rml::Style::FlexWrap::Nowrap);
|
||||
break;
|
||||
case FlexWrap::Wrap:
|
||||
set_property(Rml::PropertyId::FlexWrap, Rml::Style::FlexWrap::Wrap);
|
||||
break;
|
||||
case FlexWrap::WrapReverse:
|
||||
set_property(Rml::PropertyId::FlexWrap, Rml::Style::FlexWrap::WrapReverse);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown flex wrap.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_align_items(AlignItems align_items) {
|
||||
set_property(Rml::PropertyId::AlignItems, to_rml(align_items));
|
||||
}
|
||||
|
||||
void Style::set_overflow(Overflow overflow) {
|
||||
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
|
||||
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
|
||||
}
|
||||
|
||||
void Style::set_overflow_x(Overflow overflow) {
|
||||
set_property(Rml::PropertyId::OverflowX, to_rml(overflow));
|
||||
}
|
||||
|
||||
void Style::set_overflow_y(Overflow overflow) {
|
||||
set_property(Rml::PropertyId::OverflowY, to_rml(overflow));
|
||||
}
|
||||
|
||||
void Style::set_font_size(float size, Unit unit) {
|
||||
set_property(Rml::PropertyId::FontSize, Rml::Property(size, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_letter_spacing(float spacing, Unit unit) {
|
||||
set_property(Rml::PropertyId::LetterSpacing, Rml::Property(spacing, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_line_height(float height, Unit unit) {
|
||||
set_property(Rml::PropertyId::LineHeight, Rml::Property(height, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_font_style(FontStyle style) {
|
||||
switch (style) {
|
||||
case FontStyle::Normal:
|
||||
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Normal);
|
||||
break;
|
||||
case FontStyle::Italic:
|
||||
set_property(Rml::PropertyId::FontStyle, Rml::Style::FontStyle::Italic);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown font style.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Style::set_font_weight(uint32_t weight) {
|
||||
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));
|
||||
}
|
||||
|
||||
void Style::set_text_transform(TextTransform text_transform) {
|
||||
set_property(Rml::PropertyId::TextTransform, to_rml(text_transform));
|
||||
}
|
||||
|
||||
void Style::set_gap(float size, Unit unit) {
|
||||
set_row_gap(size, unit);
|
||||
set_column_gap(size, unit);
|
||||
}
|
||||
|
||||
void Style::set_row_gap(float size, Unit unit) {
|
||||
set_property(Rml::PropertyId::RowGap, Rml::Property(size, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_column_gap(float size, Unit unit) {
|
||||
set_property(Rml::PropertyId::ColumnGap, Rml::Property(size, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_drag(Drag drag) {
|
||||
set_property(Rml::PropertyId::Drag, to_rml(drag));
|
||||
}
|
||||
|
||||
void Style::set_tab_index(TabIndex tab_index) {
|
||||
set_property(Rml::PropertyId::TabIndex, to_rml(tab_index));
|
||||
}
|
||||
|
||||
void Style::set_font_family(std::string_view family) {
|
||||
set_property(Rml::PropertyId::FontFamily, Rml::Property(Rml::String{ family }, Rml::Unit::UNKNOWN));
|
||||
}
|
||||
|
||||
void Style::set_nav_auto(NavDirection dir) {
|
||||
set_property(nav_to_property(dir), Rml::Style::Nav::Auto);
|
||||
}
|
||||
|
||||
void Style::set_nav_none(NavDirection dir) {
|
||||
set_property(nav_to_property(dir), Rml::Style::Nav::None);
|
||||
}
|
||||
|
||||
void Style::set_nav(NavDirection dir, Element* element) {
|
||||
set_property(nav_to_property(dir), Rml::Property(Rml::String{ "#" + element->get_id() }, Rml::Unit::STRING));
|
||||
}
|
||||
|
||||
void Style::set_nav_manual(NavDirection dir, const std::string& target) {
|
||||
set_property(nav_to_property(dir), Rml::Property(target, Rml::Unit::STRING));
|
||||
}
|
||||
|
||||
void Style::set_tab_index_auto() {
|
||||
set_property(Rml::PropertyId::TabIndex, Rml::Style::Nav::Auto);
|
||||
}
|
||||
|
||||
void Style::set_tab_index_none() {
|
||||
set_property(Rml::PropertyId::TabIndex, Rml::Style::Nav::None);
|
||||
}
|
||||
|
||||
void Style::set_focusable(bool focusable) {
|
||||
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
|
||||
}
|
||||
|
||||
Rml::TransformPtr Style::get_existing_transform() {
|
||||
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
|
||||
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
|
||||
if (curTransform != nullptr) {
|
||||
return curTransform;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Style::set_or_add_transformation(const Rml::TransformPrimitive& primitive) {
|
||||
Rml::TransformPtr transform = Rml::MakeShared<Rml::Transform>();
|
||||
Rml::TransformPtr existing_transform = get_existing_transform();
|
||||
bool added_new = false;
|
||||
if (existing_transform != nullptr) {
|
||||
auto& primitives = existing_transform->GetPrimitives();
|
||||
for (int i = 0; i < primitives.size(); i++) {
|
||||
if (primitives[i].type == primitive.type) {
|
||||
transform->AddPrimitive(primitive);
|
||||
added_new = true;
|
||||
} else {
|
||||
transform->AddPrimitive(primitives[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!added_new) {
|
||||
transform->AddPrimitive(primitive);
|
||||
}
|
||||
|
||||
set_property(Rml::PropertyId::Transform, Rml::Property(Rml::Variant(std::move(transform)), Rml::Unit::TRANSFORM));
|
||||
}
|
||||
|
||||
void Style::set_translate_2D(float x, float y, Unit unit) {
|
||||
set_or_add_transformation(Rml::Transforms::Translate2D(x, y, to_rml(unit)));
|
||||
}
|
||||
|
||||
void Style::set_scale_2D(float scale_x, float scale_y) {
|
||||
set_or_add_transformation(Rml::Transforms::Scale2D(scale_x, scale_y));
|
||||
}
|
||||
|
||||
void Style::set_rotation(float degrees) {
|
||||
set_or_add_transformation(Rml::Transforms::Rotate2D(degrees));
|
||||
}
|
||||
|
||||
void Style::set_pointer_events(PointerEvents pointer_events) {
|
||||
switch (pointer_events) {
|
||||
case PointerEvents::None:
|
||||
set_property(Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None);
|
||||
break;
|
||||
case PointerEvents::Auto:
|
||||
set_property(Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::Auto);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,129 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "RmlUi/Core.h"
|
||||
|
||||
#include "../core/ui_resource.h"
|
||||
#include "ui_types.h"
|
||||
#include "ui_theme.h"
|
||||
|
||||
namespace recompui {
|
||||
const int ThemeDefaultOpacity = -1; // Represents using the theme color's existing opacity.
|
||||
|
||||
class ContextId;
|
||||
class Style {
|
||||
friend class Element; // For access to property_map without making it visible to element subclasses.
|
||||
friend class ContextId;
|
||||
private:
|
||||
std::map<Rml::PropertyId, Rml::Property> property_map;
|
||||
Rml::TransformPtr get_existing_transform();
|
||||
void set_or_add_transformation(const Rml::TransformPrimitive& primitive);
|
||||
protected:
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property);
|
||||
ResourceId resource_id = ResourceId::null();
|
||||
public:
|
||||
Style();
|
||||
virtual ~Style();
|
||||
void set_visibility(Visibility visibility);
|
||||
void set_position(Position position);
|
||||
void set_left(float left, Unit unit = Unit::Dp);
|
||||
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);
|
||||
void set_height_auto();
|
||||
void set_min_width(float width, Unit unit = Unit::Dp);
|
||||
void set_min_height(float height, Unit unit = Unit::Dp);
|
||||
void set_max_width(float width, Unit unit = Unit::Dp);
|
||||
void set_max_height(float height, Unit unit = Unit::Dp);
|
||||
void set_padding(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_left(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_top(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_right(float padding, Unit unit = Unit::Dp);
|
||||
void set_padding_bottom(float padding, Unit unit = Unit::Dp);
|
||||
void set_margin(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_left(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_top(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_right(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_bottom(float margin, Unit unit = Unit::Dp);
|
||||
void set_margin_auto();
|
||||
void set_margin_left_auto();
|
||||
void set_margin_top_auto();
|
||||
void set_margin_right_auto();
|
||||
void set_margin_bottom_auto();
|
||||
void set_border_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_left_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_top_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_right_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_bottom_width(float width, Unit unit = Unit::Dp);
|
||||
void set_border_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_top_left_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_top_right_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_bottom_left_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_border_bottom_right_radius(float radius, Unit unit = Unit::Dp);
|
||||
void set_background_color(const Color &color);
|
||||
void set_border_color(const Color &color);
|
||||
void set_border_left_color(const Color &color);
|
||||
void set_border_top_color(const Color &color);
|
||||
void set_border_right_color(const Color &color);
|
||||
void set_border_bottom_color(const Color &color);
|
||||
void set_color(const Color &color);
|
||||
void set_image_color(const Color &color);
|
||||
void set_background_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_left_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_top_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_right_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_border_bottom_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_image_color(recompui::theme::color color, int opacity = ThemeDefaultOpacity);
|
||||
void set_cursor(Cursor cursor);
|
||||
void set_opacity(float opacity);
|
||||
void set_display(Display display);
|
||||
void set_justify_content(JustifyContent justify_content);
|
||||
void set_flex_grow(float grow);
|
||||
void set_flex_shrink(float shrink);
|
||||
void set_flex_basis_auto();
|
||||
void set_flex_basis(float basis, Unit unit = Unit::Percent);
|
||||
void set_flex(float grow, float shrink);
|
||||
void set_flex(float grow, float shrink, float basis, Unit basis_unit = Unit::Percent);
|
||||
void set_flex_direction(FlexDirection flex_direction);
|
||||
void set_flex_wrap(FlexWrap flex_wrap);
|
||||
void set_align_items(AlignItems align_items);
|
||||
void set_overflow(Overflow overflow);
|
||||
void set_overflow_x(Overflow overflow);
|
||||
void set_overflow_y(Overflow overflow);
|
||||
void set_font_size(float size, Unit unit = Unit::Dp);
|
||||
void set_letter_spacing(float spacing, Unit unit = Unit::Dp);
|
||||
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);
|
||||
void set_row_gap(float size, Unit unit = Unit::Dp);
|
||||
void set_column_gap(float size, Unit unit = Unit::Dp);
|
||||
void set_drag(Drag drag);
|
||||
void set_tab_index(TabIndex focus);
|
||||
void set_font_family(std::string_view family);
|
||||
void set_translate_2D(float x, float y, Unit unit = Unit::Dp);
|
||||
void set_scale_2D(float scale_x, float scale_y);
|
||||
void set_rotation(float degrees);
|
||||
virtual void set_nav_auto(NavDirection dir);
|
||||
virtual void set_nav_none(NavDirection dir);
|
||||
virtual void set_nav(NavDirection dir, Element* element);
|
||||
virtual void set_nav_manual(NavDirection dir, const std::string& target);
|
||||
void set_tab_index_auto();
|
||||
void set_tab_index_none();
|
||||
void set_focusable(bool focusable);
|
||||
void set_pointer_events(PointerEvents pointer_events);
|
||||
virtual bool is_element() { return false; }
|
||||
ResourceId get_resource_id() { return resource_id; }
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "ui_svg.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
Svg::Svg(Element *parent, std::string_view src) : Element(parent, 0, "svg") {
|
||||
set_src(src);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Svg : public Element {
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "Svg"; }
|
||||
public:
|
||||
Svg(Element *parent, std::string_view src);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,168 +0,0 @@
|
||||
#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
|
||||
@@ -1,52 +0,0 @@
|
||||
#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
|
||||
@@ -1,65 +0,0 @@
|
||||
#include "ui_text_input.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
void TextInput::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Text: {
|
||||
const EventText &event = std::get<EventText>(e.variant);
|
||||
text = event.text;
|
||||
|
||||
for (const auto &function : text_changed_callbacks) {
|
||||
function(text);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EventType::Focus: {
|
||||
const EventFocus &event = std::get<EventFocus>(e.variant);
|
||||
if (focus_callback != nullptr) {
|
||||
focus_callback(event.active);
|
||||
}
|
||||
set_style_enabled(focus_state, event.active);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TextInput::TextInput(Element *parent, bool text_visible) : Element(parent, Events(EventType::Text, EventType::Focus), "input") {
|
||||
if (!text_visible) {
|
||||
set_attribute("type", "password");
|
||||
}
|
||||
set_min_width(60.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) {
|
||||
this->text = std::string(text);
|
||||
set_attribute("value", this->text);
|
||||
}
|
||||
|
||||
const std::string &TextInput::get_text() {
|
||||
return text;
|
||||
}
|
||||
|
||||
void TextInput::add_text_changed_callback(std::function<void(const std::string &)> callback) {
|
||||
text_changed_callbacks.emplace_back(callback);
|
||||
}
|
||||
|
||||
void TextInput::set_focus_callback(std::function<void(bool)> callback) {
|
||||
focus_callback = callback;
|
||||
}
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class TextInput : public Element {
|
||||
private:
|
||||
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"; }
|
||||
public:
|
||||
TextInput(Element *parent, bool text_visible = true);
|
||||
void set_text(std::string_view text);
|
||||
const std::string &get_text();
|
||||
void add_text_changed_callback(std::function<void(const std::string &)> callback);
|
||||
void set_focus_callback(std::function<void(bool)> callback);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,267 +0,0 @@
|
||||
#include <array>
|
||||
#include "ui_theme.h"
|
||||
|
||||
using ThemeColor = recompui::theme::color;
|
||||
using ThemeColorArray = std::array<recompui::Color, (std::size_t)(ThemeColor::size)>;
|
||||
using ThemeColorNameArray = std::array<const char *, (std::size_t)(ThemeColor::size)>;
|
||||
|
||||
float recompui::theme::border::radius_sm = 8.0f;
|
||||
float recompui::theme::border::radius_md = 12.0f;
|
||||
float recompui::theme::border::radius_lg = 16.0f;
|
||||
float recompui::theme::border::width = 1.1f;
|
||||
|
||||
constexpr ThemeColorArray get_default_theme_color_array() {
|
||||
ThemeColorArray colors;
|
||||
|
||||
colors[(std::size_t)ThemeColor::Background1] = recompui::Color{2, 7, 18, 255};
|
||||
colors[(std::size_t)ThemeColor::Background2] = recompui::Color{7, 15, 34, 255};
|
||||
colors[(std::size_t)ThemeColor::Background3] = recompui::Color{18, 24, 38, 255};
|
||||
colors[(std::size_t)ThemeColor::BGOverlay] = recompui::Color{182, 194, 221, 26};
|
||||
colors[(std::size_t)ThemeColor::ModalOverlay] = recompui::Color{2, 7, 18, 229};
|
||||
|
||||
colors[(std::size_t)ThemeColor::BGShadow] = recompui::Color{0, 0, 0, 89};
|
||||
colors[(std::size_t)ThemeColor::BGShadow2] = recompui::Color{2, 7, 18, 184};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Text] = recompui::Color{242, 242, 242, 255};
|
||||
colors[(std::size_t)ThemeColor::TextActive] = recompui::Color{245, 245, 245, 255};
|
||||
colors[(std::size_t)ThemeColor::TextDim] = recompui::Color{204, 204, 204, 255};
|
||||
colors[(std::size_t)ThemeColor::TextInactive] = recompui::Color{255, 255, 255, 153};
|
||||
colors[(std::size_t)ThemeColor::TextA5] = recompui::Color{242, 242, 242, 13};
|
||||
colors[(std::size_t)ThemeColor::TextA20] = recompui::Color{242, 242, 242, 51};
|
||||
colors[(std::size_t)ThemeColor::TextA30] = recompui::Color{242, 242, 242, 77};
|
||||
colors[(std::size_t)ThemeColor::TextA50] = recompui::Color{242, 242, 242, 128};
|
||||
colors[(std::size_t)ThemeColor::TextA80] = recompui::Color{242, 242, 242, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Primary] = recompui::Color{29, 93, 226, 255};
|
||||
colors[(std::size_t)ThemeColor::PrimaryL] = recompui::Color{167, 191, 241, 255};
|
||||
colors[(std::size_t)ThemeColor::PrimaryD] = recompui::Color{0, 38, 117, 255};
|
||||
colors[(std::size_t)ThemeColor::PrimaryA5] = recompui::Color{29, 93, 226, 13};
|
||||
colors[(std::size_t)ThemeColor::PrimaryA20] = recompui::Color{29, 93, 226, 51};
|
||||
colors[(std::size_t)ThemeColor::PrimaryA30] = recompui::Color{29, 93, 226, 77};
|
||||
colors[(std::size_t)ThemeColor::PrimaryA50] = recompui::Color{29, 93, 226, 128};
|
||||
colors[(std::size_t)ThemeColor::PrimaryA80] = recompui::Color{29, 93, 226, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Secondary] = recompui::Color{247, 158, 8, 255};
|
||||
colors[(std::size_t)ThemeColor::SecondaryL] = recompui::Color{255, 215, 148, 255};
|
||||
colors[(std::size_t)ThemeColor::SecondaryD] = recompui::Color{224, 141, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::SecondaryA5] = recompui::Color{247, 158, 8, 13};
|
||||
colors[(std::size_t)ThemeColor::SecondaryA20] = recompui::Color{247, 158, 8, 51};
|
||||
colors[(std::size_t)ThemeColor::SecondaryA30] = recompui::Color{247, 158, 8, 77};
|
||||
colors[(std::size_t)ThemeColor::SecondaryA50] = recompui::Color{247, 158, 8, 128};
|
||||
colors[(std::size_t)ThemeColor::SecondaryA80] = recompui::Color{247, 158, 8, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Warning] = recompui::Color{255, 254, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::WarningL] = recompui::Color{255, 254, 143, 255};
|
||||
colors[(std::size_t)ThemeColor::WarningD] = recompui::Color{197, 163, 2, 255};
|
||||
colors[(std::size_t)ThemeColor::WarningA5] = recompui::Color{255, 254, 0, 13};
|
||||
colors[(std::size_t)ThemeColor::WarningA20] = recompui::Color{255, 254, 0, 51};
|
||||
colors[(std::size_t)ThemeColor::WarningA30] = recompui::Color{255, 254, 0, 77};
|
||||
colors[(std::size_t)ThemeColor::WarningA50] = recompui::Color{255, 254, 0, 128};
|
||||
colors[(std::size_t)ThemeColor::WarningA80] = recompui::Color{255, 254, 0, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Danger] = recompui::Color{255, 53, 31, 255};
|
||||
colors[(std::size_t)ThemeColor::DangerL] = recompui::Color{255, 149, 138, 255};
|
||||
colors[(std::size_t)ThemeColor::DangerD] = recompui::Color{163, 16, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::DangerA5] = recompui::Color{255, 53, 31, 13};
|
||||
colors[(std::size_t)ThemeColor::DangerA20] = recompui::Color{255, 53, 31, 51};
|
||||
colors[(std::size_t)ThemeColor::DangerA30] = recompui::Color{255, 53, 31, 77};
|
||||
colors[(std::size_t)ThemeColor::DangerA50] = recompui::Color{255, 53, 31, 128};
|
||||
colors[(std::size_t)ThemeColor::DangerA80] = recompui::Color{255, 53, 31, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Success] = recompui::Color{40, 238, 32, 255};
|
||||
colors[(std::size_t)ThemeColor::SuccessL] = recompui::Color{155, 247, 151, 255};
|
||||
colors[(std::size_t)ThemeColor::SuccessD] = recompui::Color{18, 157, 12, 255};
|
||||
colors[(std::size_t)ThemeColor::SuccessA5] = recompui::Color{40, 238, 32, 13};
|
||||
colors[(std::size_t)ThemeColor::SuccessA20] = recompui::Color{40, 238, 32, 51};
|
||||
colors[(std::size_t)ThemeColor::SuccessA30] = recompui::Color{40, 238, 32, 77};
|
||||
colors[(std::size_t)ThemeColor::SuccessA50] = recompui::Color{40, 238, 32, 128};
|
||||
colors[(std::size_t)ThemeColor::SuccessA80] = recompui::Color{40, 238, 32, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Border] = recompui::Color{255, 255, 255, 51};
|
||||
colors[(std::size_t)ThemeColor::BorderSoft] = recompui::Color{255, 255, 255, 26};
|
||||
colors[(std::size_t)ThemeColor::BorderHard] = recompui::Color{255, 255, 255, 77};
|
||||
colors[(std::size_t)ThemeColor::BorderSolid] = recompui::Color{255, 255, 255, 153};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Transparent] = recompui::Color{0, 0, 0, 0};
|
||||
|
||||
colors[(std::size_t)ThemeColor::A] = recompui::Color{51, 51, 255, 255};
|
||||
colors[(std::size_t)ThemeColor::AL] = recompui::Color{178, 178, 255, 255};
|
||||
colors[(std::size_t)ThemeColor::AD] = recompui::Color{32, 32, 172, 255};
|
||||
colors[(std::size_t)ThemeColor::AA5] = recompui::Color{51, 51, 255, 13};
|
||||
colors[(std::size_t)ThemeColor::AA20] = recompui::Color{51, 51, 255, 51};
|
||||
colors[(std::size_t)ThemeColor::AA30] = recompui::Color{51, 51, 255, 77};
|
||||
colors[(std::size_t)ThemeColor::AA50] = recompui::Color{51, 51, 255, 128};
|
||||
colors[(std::size_t)ThemeColor::AA80] = recompui::Color{51, 51, 255, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::White] = recompui::Color{255, 255, 255, 255};
|
||||
colors[(std::size_t)ThemeColor::WhiteA5] = recompui::Color{255, 255, 255, 13};
|
||||
colors[(std::size_t)ThemeColor::WhiteA20] = recompui::Color{255, 255, 255, 51};
|
||||
colors[(std::size_t)ThemeColor::WhiteA30] = recompui::Color{255, 255, 255, 77};
|
||||
colors[(std::size_t)ThemeColor::WhiteA50] = recompui::Color{255, 255, 255, 128};
|
||||
colors[(std::size_t)ThemeColor::WhiteA80] = recompui::Color{255, 255, 255, 204};
|
||||
|
||||
colors[(std::size_t)ThemeColor::BW05] = recompui::Color{13, 13, 13, 255};
|
||||
colors[(std::size_t)ThemeColor::BW10] = recompui::Color{26, 26, 26, 255};
|
||||
colors[(std::size_t)ThemeColor::BW25] = recompui::Color{64, 64, 64, 255};
|
||||
colors[(std::size_t)ThemeColor::BW50] = recompui::Color{128, 128, 128, 255};
|
||||
colors[(std::size_t)ThemeColor::BW75] = recompui::Color{191, 191, 191, 255};
|
||||
colors[(std::size_t)ThemeColor::BW90] = recompui::Color{229, 229, 229, 255};
|
||||
|
||||
colors[(std::size_t)ThemeColor::Player1] = recompui::Color{0, 185, 253, 255};
|
||||
colors[(std::size_t)ThemeColor::Player2] = recompui::Color{253, 76, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::Player3] = recompui::Color{253, 194, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::Player4] = recompui::Color{147, 253, 0, 255};
|
||||
colors[(std::size_t)ThemeColor::Player5] = recompui::Color{0, 253, 164, 255};
|
||||
colors[(std::size_t)ThemeColor::Player6] = recompui::Color{253, 0, 189, 255};
|
||||
colors[(std::size_t)ThemeColor::Player7] = recompui::Color{140, 74, 255, 255};
|
||||
colors[(std::size_t)ThemeColor::Player8] = recompui::Color{255, 186, 118, 255};
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
constexpr ThemeColorNameArray get_default_theme_color_names() {
|
||||
ThemeColorNameArray names = {};
|
||||
names[(std::size_t)ThemeColor::Background1] = "Background1";
|
||||
names[(std::size_t)ThemeColor::Background2] = "Background2";
|
||||
names[(std::size_t)ThemeColor::Background3] = "Background3";
|
||||
names[(std::size_t)ThemeColor::BGOverlay] = "BGOverlay";
|
||||
names[(std::size_t)ThemeColor::ModalOverlay] = "ModalOverlay";
|
||||
names[(std::size_t)ThemeColor::BGShadow] = "BGShadow";
|
||||
names[(std::size_t)ThemeColor::BGShadow2] = "BGShadow2";
|
||||
names[(std::size_t)ThemeColor::Text] = "Text";
|
||||
names[(std::size_t)ThemeColor::TextActive] = "TextActive";
|
||||
names[(std::size_t)ThemeColor::TextDim] = "TextDim";
|
||||
names[(std::size_t)ThemeColor::TextInactive] = "TextInactive";
|
||||
names[(std::size_t)ThemeColor::TextA5] = "TextA5";
|
||||
names[(std::size_t)ThemeColor::TextA20] = "TextA20";
|
||||
names[(std::size_t)ThemeColor::TextA30] = "TextA30";
|
||||
names[(std::size_t)ThemeColor::TextA50] = "TextA50";
|
||||
names[(std::size_t)ThemeColor::TextA80] = "TextA80";
|
||||
names[(std::size_t)ThemeColor::Primary] = "Primary";
|
||||
names[(std::size_t)ThemeColor::PrimaryL] = "PrimaryL";
|
||||
names[(std::size_t)ThemeColor::PrimaryD] = "PrimaryD";
|
||||
names[(std::size_t)ThemeColor::PrimaryA5] = "PrimaryA5";
|
||||
names[(std::size_t)ThemeColor::PrimaryA20] = "PrimaryA20";
|
||||
names[(std::size_t)ThemeColor::PrimaryA30] = "PrimaryA30";
|
||||
names[(std::size_t)ThemeColor::PrimaryA50] = "PrimaryA50";
|
||||
names[(std::size_t)ThemeColor::PrimaryA80] = "PrimaryA80";
|
||||
names[(std::size_t)ThemeColor::Secondary] = "Secondary";
|
||||
names[(std::size_t)ThemeColor::SecondaryL] = "SecondaryL";
|
||||
names[(std::size_t)ThemeColor::SecondaryD] = "SecondaryD";
|
||||
names[(std::size_t)ThemeColor::SecondaryA5] = "SecondaryA5";
|
||||
names[(std::size_t)ThemeColor::SecondaryA20] = "SecondaryA20";
|
||||
names[(std::size_t)ThemeColor::SecondaryA30] = "SecondaryA30";
|
||||
names[(std::size_t)ThemeColor::SecondaryA50] = "SecondaryA50";
|
||||
names[(std::size_t)ThemeColor::SecondaryA80] = "SecondaryA80";
|
||||
names[(std::size_t)ThemeColor::Warning] = "Warning";
|
||||
names[(std::size_t)ThemeColor::WarningL] = "WarningL";
|
||||
names[(std::size_t)ThemeColor::WarningD] = "WarningD";
|
||||
names[(std::size_t)ThemeColor::WarningA5] = "WarningA5";
|
||||
names[(std::size_t)ThemeColor::WarningA20] = "WarningA20";
|
||||
names[(std::size_t)ThemeColor::WarningA30] = "WarningA30";
|
||||
names[(std::size_t)ThemeColor::WarningA50] = "WarningA50";
|
||||
names[(std::size_t)ThemeColor::WarningA80] = "WarningA80";
|
||||
names[(std::size_t)ThemeColor::Danger] = "Danger";
|
||||
names[(std::size_t)ThemeColor::DangerL] = "DangerL";
|
||||
names[(std::size_t)ThemeColor::DangerD] = "DangerD";
|
||||
names[(std::size_t)ThemeColor::DangerA5] = "DangerA5";
|
||||
names[(std::size_t)ThemeColor::DangerA20] = "DangerA20";
|
||||
names[(std::size_t)ThemeColor::DangerA30] = "DangerA30";
|
||||
names[(std::size_t)ThemeColor::DangerA50] = "DangerA50";
|
||||
names[(std::size_t)ThemeColor::DangerA80] = "DangerA80";
|
||||
names[(std::size_t)ThemeColor::Success] = "Success";
|
||||
names[(std::size_t)ThemeColor::SuccessL] = "SuccessL";
|
||||
names[(std::size_t)ThemeColor::SuccessD] = "SuccessD";
|
||||
names[(std::size_t)ThemeColor::SuccessA5] = "SuccessA5";
|
||||
names[(std::size_t)ThemeColor::SuccessA20] = "SuccessA20";
|
||||
names[(std::size_t)ThemeColor::SuccessA30] = "SuccessA30";
|
||||
names[(std::size_t)ThemeColor::SuccessA50] = "SuccessA50";
|
||||
names[(std::size_t)ThemeColor::SuccessA80] = "SuccessA80";
|
||||
names[(std::size_t)ThemeColor::Border] = "Border";
|
||||
names[(std::size_t)ThemeColor::BorderSoft] = "BorderSoft";
|
||||
names[(std::size_t)ThemeColor::BorderHard] = "BorderHard";
|
||||
names[(std::size_t)ThemeColor::BorderSolid] = "BorderSolid";
|
||||
names[(std::size_t)ThemeColor::Transparent] = "Transparent";
|
||||
names[(std::size_t)ThemeColor::A] = "A";
|
||||
names[(std::size_t)ThemeColor::AL] = "AL";
|
||||
names[(std::size_t)ThemeColor::AD] = "AD";
|
||||
names[(std::size_t)ThemeColor::AA5] = "AA5";
|
||||
names[(std::size_t)ThemeColor::AA20] = "AA20";
|
||||
names[(std::size_t)ThemeColor::AA30] = "AA30";
|
||||
names[(std::size_t)ThemeColor::AA50] = "AA50";
|
||||
names[(std::size_t)ThemeColor::AA80] = "AA80";
|
||||
names[(std::size_t)ThemeColor::White] = "White";
|
||||
names[(std::size_t)ThemeColor::WhiteA5] = "WhiteA5";
|
||||
names[(std::size_t)ThemeColor::WhiteA20] = "WhiteA20";
|
||||
names[(std::size_t)ThemeColor::WhiteA30] = "WhiteA30";
|
||||
names[(std::size_t)ThemeColor::WhiteA50] = "WhiteA50";
|
||||
names[(std::size_t)ThemeColor::WhiteA80] = "WhiteA80";
|
||||
names[(std::size_t)ThemeColor::BW05] = "BW05";
|
||||
names[(std::size_t)ThemeColor::BW10] = "BW10";
|
||||
names[(std::size_t)ThemeColor::BW25] = "BW25";
|
||||
names[(std::size_t)ThemeColor::BW50] = "BW50";
|
||||
names[(std::size_t)ThemeColor::BW75] = "BW75";
|
||||
names[(std::size_t)ThemeColor::BW90] = "BW90";
|
||||
names[(std::size_t)ThemeColor::Player1] = "Player1";
|
||||
names[(std::size_t)ThemeColor::Player2] = "Player2";
|
||||
names[(std::size_t)ThemeColor::Player3] = "Player3";
|
||||
names[(std::size_t)ThemeColor::Player4] = "Player4";
|
||||
names[(std::size_t)ThemeColor::Player5] = "Player5";
|
||||
names[(std::size_t)ThemeColor::Player6] = "Player6";
|
||||
names[(std::size_t)ThemeColor::Player7] = "Player7";
|
||||
names[(std::size_t)ThemeColor::Player8] = "Player8";
|
||||
return names;
|
||||
}
|
||||
|
||||
static ThemeColorArray theme_colors = get_default_theme_color_array();
|
||||
static ThemeColorNameArray theme_color_names = get_default_theme_color_names();
|
||||
|
||||
void recompui::theme::set_theme_color(recompui::theme::color color, const recompui::Color &value) {
|
||||
theme_colors[(std::size_t)color] = value;
|
||||
}
|
||||
|
||||
const recompui::Color &recompui::theme::get_theme_color(recompui::theme::color color) {
|
||||
return theme_colors[(std::size_t)color];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_types.h"
|
||||
|
||||
namespace recompui {
|
||||
namespace theme {
|
||||
enum class color {
|
||||
Background1,
|
||||
Background2,
|
||||
Background3,
|
||||
BGOverlay,
|
||||
ModalOverlay,
|
||||
BGShadow,
|
||||
BGShadow2,
|
||||
Text,
|
||||
TextActive,
|
||||
TextDim,
|
||||
TextInactive,
|
||||
TextA5,
|
||||
TextA20,
|
||||
TextA30,
|
||||
TextA50,
|
||||
TextA80,
|
||||
Primary,
|
||||
PrimaryL,
|
||||
PrimaryD,
|
||||
PrimaryA5,
|
||||
PrimaryA20,
|
||||
PrimaryA30,
|
||||
PrimaryA50,
|
||||
PrimaryA80,
|
||||
Secondary,
|
||||
SecondaryL,
|
||||
SecondaryD,
|
||||
SecondaryA5,
|
||||
SecondaryA20,
|
||||
SecondaryA30,
|
||||
SecondaryA50,
|
||||
SecondaryA80,
|
||||
Warning,
|
||||
WarningL,
|
||||
WarningD,
|
||||
WarningA5,
|
||||
WarningA20,
|
||||
WarningA30,
|
||||
WarningA50,
|
||||
WarningA80,
|
||||
Danger,
|
||||
DangerL,
|
||||
DangerD,
|
||||
DangerA5,
|
||||
DangerA20,
|
||||
DangerA30,
|
||||
DangerA50,
|
||||
DangerA80,
|
||||
Success,
|
||||
SuccessL,
|
||||
SuccessD,
|
||||
SuccessA5,
|
||||
SuccessA20,
|
||||
SuccessA30,
|
||||
SuccessA50,
|
||||
SuccessA80,
|
||||
Border,
|
||||
BorderSoft,
|
||||
BorderHard,
|
||||
BorderSolid,
|
||||
Transparent,
|
||||
A,
|
||||
AL,
|
||||
AD,
|
||||
AA5,
|
||||
AA20,
|
||||
AA30,
|
||||
AA50,
|
||||
AA80,
|
||||
White,
|
||||
WhiteA5,
|
||||
WhiteA20,
|
||||
WhiteA30,
|
||||
WhiteA50,
|
||||
WhiteA80,
|
||||
BW05,
|
||||
BW10,
|
||||
BW25,
|
||||
BW50,
|
||||
BW75,
|
||||
BW90,
|
||||
Player1,
|
||||
Player2,
|
||||
Player3,
|
||||
Player4,
|
||||
Player5,
|
||||
Player6,
|
||||
Player7,
|
||||
Player8,
|
||||
|
||||
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;
|
||||
extern float radius_lg;
|
||||
extern float width;
|
||||
}
|
||||
|
||||
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,238 +0,0 @@
|
||||
#include "ui_toggle.h"
|
||||
#include "ui_pseudo_border.h"
|
||||
#include "../ui_utils.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <ultramodern/ultramodern.hpp>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
struct ToggleSizing {
|
||||
float width;
|
||||
float height;
|
||||
float floater_width;
|
||||
float floater_height;
|
||||
float floater_margin;
|
||||
|
||||
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_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_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);
|
||||
add_style(&checked_hover_style, { checked_state, hover_state });
|
||||
add_style(&checked_focus_style, { checked_state, focus_state });
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&checked_disabled_style, { checked_state, disabled_state });
|
||||
|
||||
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::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);
|
||||
floater_disabled_checked_style.set_background_color(theme::color::PrimaryD, 128);
|
||||
floater->add_style(&floater_checked_style, checked_state);
|
||||
floater->add_style(&floater_disabled_style, disabled_state);
|
||||
floater->add_style(&floater_disabled_checked_style, { checked_state, disabled_state });
|
||||
|
||||
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;
|
||||
|
||||
if (animate) {
|
||||
last_time = ultramodern::time_since_start();
|
||||
queue_update();
|
||||
}
|
||||
else {
|
||||
floater_left = floater_left_target();
|
||||
}
|
||||
|
||||
floater->set_left(floater_left, Unit::Dp);
|
||||
|
||||
if (trigger_callbacks) {
|
||||
for (const auto &function : checked_callbacks) {
|
||||
function(checked);
|
||||
}
|
||||
}
|
||||
|
||||
set_all_style_enabled(checked_state, checked);
|
||||
}
|
||||
}
|
||||
|
||||
float Toggle::floater_left_target() const {
|
||||
return get_toggle_sizing(size).left_offset(checked);
|
||||
}
|
||||
|
||||
void Toggle::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (is_enabled()) {
|
||||
set_checked_internal(!checked, true, false, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case EventType::Hover: {
|
||||
bool hover_active = std::get<EventHover>(e.variant).active && is_enabled();
|
||||
set_all_style_enabled(hover_state, hover_active);
|
||||
break;
|
||||
}
|
||||
case EventType::Focus: {
|
||||
bool focus_active = std::get<EventFocus>(e.variant).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_all_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::Update: {
|
||||
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();
|
||||
if (target < floater_left) {
|
||||
floater_left += std::max(-dp_speed * delta_time, target - floater_left);
|
||||
}
|
||||
else {
|
||||
floater_left += std::min(dp_speed * delta_time, target - floater_left);
|
||||
}
|
||||
|
||||
if (abs(target - floater_left) < 1e-4f) {
|
||||
floater_left = target;
|
||||
}
|
||||
else {
|
||||
should_queue_update = true;
|
||||
}
|
||||
|
||||
floater->set_left(floater_left, Unit::Dp);
|
||||
|
||||
if (should_queue_update) {
|
||||
queue_update();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Toggle::set_checked(bool checked) {
|
||||
set_checked_internal(checked, false, false, false);
|
||||
}
|
||||
|
||||
bool Toggle::is_checked() const {
|
||||
return checked;
|
||||
}
|
||||
|
||||
void Toggle::add_checked_callback(std::function<void(bool)> callback) {
|
||||
checked_callbacks.emplace_back(callback);
|
||||
}
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_element.h"
|
||||
|
||||
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;
|
||||
Style checked_style;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
Style checked_hover_style;
|
||||
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, ToggleSize size = ToggleSize::Default);
|
||||
void set_checked(bool checked);
|
||||
bool is_checked() const;
|
||||
void add_checked_callback(std::function<void(bool)> callback);
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,317 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <stdint.h>
|
||||
#include <variant>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
constexpr std::string_view checked_state = "checked";
|
||||
constexpr std::string_view hover_state = "hover";
|
||||
constexpr std::string_view focus_state = "focus";
|
||||
constexpr std::string_view disabled_state = "disabled";
|
||||
|
||||
struct Color {
|
||||
uint8_t r = 255;
|
||||
uint8_t g = 255;
|
||||
uint8_t b = 255;
|
||||
uint8_t a = 255;
|
||||
};
|
||||
|
||||
enum class Cursor {
|
||||
None,
|
||||
Pointer
|
||||
};
|
||||
|
||||
// These two enums must be kept in sync with patches/recompui_event_structs.h!
|
||||
enum class EventType {
|
||||
None,
|
||||
Click,
|
||||
Focus,
|
||||
Hover,
|
||||
Enable,
|
||||
Drag,
|
||||
Text,
|
||||
Update,
|
||||
Navigate,
|
||||
MouseButton,
|
||||
MenuAction,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class DragPhase {
|
||||
None,
|
||||
Start,
|
||||
Move,
|
||||
End
|
||||
};
|
||||
|
||||
enum class NavDirection {
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Left
|
||||
};
|
||||
|
||||
enum class MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class PointerEvents {
|
||||
None,
|
||||
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);
|
||||
}
|
||||
|
||||
template <typename Enum, typename... Enums, typename = std::enable_if_t<std::is_enum_v<Enum>>>
|
||||
constexpr uint32_t Events(Enum first, Enums... rest) {
|
||||
return Events(first) | Events(rest...);
|
||||
}
|
||||
|
||||
struct EventClick {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct EventFocus {
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct EventHover {
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct EventEnable {
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct EventDrag {
|
||||
float x;
|
||||
float y;
|
||||
DragPhase phase;
|
||||
};
|
||||
|
||||
struct EventText {
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct EventNavigate {
|
||||
NavDirection direction;
|
||||
};
|
||||
|
||||
struct EventMouseButton {
|
||||
float x;
|
||||
float y;
|
||||
MouseButton button;
|
||||
bool pressed;
|
||||
};
|
||||
|
||||
struct EventMenuAction {
|
||||
MenuAction action;
|
||||
};
|
||||
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, EventNavigate, EventMouseButton, EventMenuAction, std::monostate>;
|
||||
|
||||
struct Event {
|
||||
EventType type;
|
||||
EventVariant variant;
|
||||
|
||||
// Factory methods for creating specific events
|
||||
static Event click_event(float x, float y) {
|
||||
Event e;
|
||||
e.type = EventType::Click;
|
||||
e.variant = EventClick{ x, y };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event focus_event(bool active) {
|
||||
Event e;
|
||||
e.type = EventType::Focus;
|
||||
e.variant = EventFocus{ active };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event hover_event(bool active) {
|
||||
Event e;
|
||||
e.type = EventType::Hover;
|
||||
e.variant = EventHover{ active };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event enable_event(bool enable) {
|
||||
Event e;
|
||||
e.type = EventType::Enable;
|
||||
e.variant = EventEnable{ enable };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event drag_event(float x, float y, DragPhase phase) {
|
||||
Event e;
|
||||
e.type = EventType::Drag;
|
||||
e.variant = EventDrag{ x, y, phase };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event text_event(const std::string &text) {
|
||||
Event e;
|
||||
e.type = EventType::Text;
|
||||
e.variant = EventText{ text };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event update_event() {
|
||||
Event e;
|
||||
e.type = EventType::Update;
|
||||
e.variant = std::monostate{};
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event navigate_event(NavDirection direction) {
|
||||
Event e;
|
||||
e.type = EventType::Navigate;
|
||||
e.variant = EventNavigate{ direction };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event mousebutton_event(float x, float y, MouseButton button, bool pressed) {
|
||||
Event e;
|
||||
e.type = EventType::MouseButton;
|
||||
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 {
|
||||
None,
|
||||
Block,
|
||||
Inline,
|
||||
InlineBlock,
|
||||
FlowRoot,
|
||||
Flex,
|
||||
InlineFlex,
|
||||
Table,
|
||||
InlineTable,
|
||||
TableRow,
|
||||
TableRowGroup,
|
||||
TableColumn,
|
||||
TableColumnGroup,
|
||||
TableCell
|
||||
};
|
||||
|
||||
enum class Visibility {
|
||||
Visible,
|
||||
Hidden
|
||||
};
|
||||
|
||||
enum class Position {
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
enum class JustifyContent {
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly
|
||||
};
|
||||
|
||||
enum class FlexDirection {
|
||||
Row,
|
||||
Column,
|
||||
RowReverse,
|
||||
ColumnReverse
|
||||
};
|
||||
|
||||
enum class FlexWrap {
|
||||
NoWrap,
|
||||
Wrap,
|
||||
WrapReverse
|
||||
};
|
||||
|
||||
enum class AlignItems {
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Center,
|
||||
Baseline,
|
||||
Stretch
|
||||
};
|
||||
|
||||
enum class Overflow {
|
||||
Visible,
|
||||
Hidden,
|
||||
Auto,
|
||||
Scroll
|
||||
};
|
||||
|
||||
enum class Unit {
|
||||
Px,
|
||||
Dp,
|
||||
Percent
|
||||
};
|
||||
|
||||
enum class AnimationType : uint32_t {
|
||||
None,
|
||||
Set,
|
||||
Tween
|
||||
};
|
||||
|
||||
enum class FontStyle {
|
||||
Normal,
|
||||
Italic
|
||||
};
|
||||
|
||||
enum class TextAlign {
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
Justify
|
||||
};
|
||||
|
||||
enum class TextTransform {
|
||||
None,
|
||||
Capitalize,
|
||||
Uppercase,
|
||||
Lowercase
|
||||
};
|
||||
|
||||
enum class Drag {
|
||||
None,
|
||||
Drag,
|
||||
DragDrop,
|
||||
Block,
|
||||
Clone
|
||||
};
|
||||
|
||||
enum class TabIndex {
|
||||
None,
|
||||
Auto
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
-1031
File diff suppressed because it is too large
Load Diff
@@ -1,124 +0,0 @@
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
#include "overloaded.h"
|
||||
#include "recomp_ui.h"
|
||||
|
||||
#include "core/ui_context.h"
|
||||
#include "core/ui_resource.h"
|
||||
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_button.h"
|
||||
#include "elements/ui_clickable.h"
|
||||
#include "elements/ui_container.h"
|
||||
#include "elements/ui_image.h"
|
||||
#include "elements/ui_label.h"
|
||||
#include "elements/ui_radio.h"
|
||||
#include "elements/ui_scroll_container.h"
|
||||
#include "elements/ui_slider.h"
|
||||
#include "elements/ui_style.h"
|
||||
#include "elements/ui_text_input.h"
|
||||
#include "elements/ui_toggle.h"
|
||||
#include "elements/ui_types.h"
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/helpers.hpp"
|
||||
|
||||
#include "../patches/ui_funcs.h"
|
||||
|
||||
struct QueuedCallback {
|
||||
recompui::ResourceId resource;
|
||||
recompui::Event event;
|
||||
recompui::UICallback callback;
|
||||
};
|
||||
|
||||
moodycamel::ConcurrentQueue<QueuedCallback> queued_callbacks{};
|
||||
|
||||
void recompui::queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback) {
|
||||
queued_callbacks.enqueue(QueuedCallback{ .resource = resource, .event = e, .callback = callback });
|
||||
}
|
||||
|
||||
bool convert_event(const recompui::Event& in, RecompuiEventData& out) {
|
||||
bool skip = false;
|
||||
out = {};
|
||||
out.type = static_cast<RecompuiEventType>(in.type);
|
||||
|
||||
switch (in.type) {
|
||||
default:
|
||||
case recompui::EventType::None:
|
||||
case recompui::EventType::Count:
|
||||
skip = true;
|
||||
break;
|
||||
case recompui::EventType::Click:
|
||||
{
|
||||
const recompui::EventClick &click = std::get<recompui::EventClick>(in.variant);
|
||||
out.data.click.x = click.x;
|
||||
out.data.click.y = click.y;
|
||||
}
|
||||
break;
|
||||
case recompui::EventType::Focus:
|
||||
{
|
||||
const recompui::EventFocus &focus = std::get<recompui::EventFocus>(in.variant);
|
||||
out.data.focus.active = focus.active;
|
||||
}
|
||||
break;
|
||||
case recompui::EventType::Hover:
|
||||
{
|
||||
const recompui::EventHover &hover = std::get<recompui::EventHover>(in.variant);
|
||||
out.data.hover.active = hover.active;
|
||||
}
|
||||
break;
|
||||
case recompui::EventType::Enable:
|
||||
{
|
||||
const recompui::EventEnable &enable = std::get<recompui::EventEnable>(in.variant);
|
||||
out.data.enable.active = enable.active;
|
||||
}
|
||||
break;
|
||||
case recompui::EventType::Drag:
|
||||
{
|
||||
const recompui::EventDrag &drag = std::get<recompui::EventDrag>(in.variant);
|
||||
out.data.drag.phase = static_cast<RecompuiDragPhase>(drag.phase);
|
||||
out.data.drag.x = drag.x;
|
||||
out.data.drag.y = drag.y;
|
||||
}
|
||||
break;
|
||||
case recompui::EventType::Text:
|
||||
skip = true; // Text events aren't supported in the UI mod API.
|
||||
break;
|
||||
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;
|
||||
}
|
||||
|
||||
extern "C" void recomp_run_ui_callbacks(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Allocate the event on the stack.
|
||||
gpr stack_frame = ctx->r29;
|
||||
ctx->r29 -= sizeof(RecompuiEventData);
|
||||
RecompuiEventData* event_data = TO_PTR(RecompuiEventData, stack_frame);
|
||||
|
||||
QueuedCallback cur_callback;
|
||||
|
||||
while (queued_callbacks.try_dequeue(cur_callback)) {
|
||||
if (convert_event(cur_callback.event, *event_data)) {
|
||||
recompui::ContextId cur_context = cur_callback.callback.context;
|
||||
cur_context.open();
|
||||
|
||||
ctx->r4 = static_cast<int32_t>(cur_callback.resource.slot_id);
|
||||
ctx->r5 = stack_frame;
|
||||
ctx->r6 = cur_callback.callback.userdata;
|
||||
|
||||
LOOKUP_FUNC(cur_callback.callback.callback)(rdram, ctx);
|
||||
cur_context.close();
|
||||
}
|
||||
}
|
||||
|
||||
ctx->r29 += sizeof(RecompuiEventData);
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
|
||||
#include "ui_helpers.h"
|
||||
#include "ui_api_images.h"
|
||||
#include "elements/ui_image.h"
|
||||
|
||||
using namespace recompui;
|
||||
|
||||
struct {
|
||||
std::mutex mutex;
|
||||
std::unordered_set<uint32_t> textures{};
|
||||
uint32_t textures_created = 0;
|
||||
} TextureState;
|
||||
|
||||
const std::string mod_texture_prefix = "?/mod_api/";
|
||||
|
||||
static std::string get_texture_name(uint32_t texture_id) {
|
||||
return mod_texture_prefix + std::to_string(texture_id);
|
||||
}
|
||||
|
||||
static uint32_t get_new_texture_id() {
|
||||
std::lock_guard lock{TextureState.mutex};
|
||||
uint32_t cur_id = TextureState.textures_created++;
|
||||
TextureState.textures.emplace(cur_id);
|
||||
|
||||
return cur_id;
|
||||
}
|
||||
|
||||
static void release_texture(uint32_t texture_id) {
|
||||
std::string texture_name = get_texture_name(texture_id);
|
||||
std::lock_guard lock{TextureState.mutex};
|
||||
|
||||
if (TextureState.textures.erase(texture_id) == 0) {
|
||||
recompui::message_box("Fatal error in mod - attempted to destroy texture that doesn't exist!");
|
||||
assert(false);
|
||||
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||
}
|
||||
|
||||
recompui::release_image(texture_name);
|
||||
}
|
||||
|
||||
thread_local std::vector<char> swapped_image_bytes;
|
||||
|
||||
void recompui_create_texture_rgba32(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) data_in = _arg<0, PTR(void)>(rdram, ctx);
|
||||
uint32_t width = _arg<1, uint32_t>(rdram, ctx);
|
||||
uint32_t height = _arg<2, uint32_t>(rdram, ctx);
|
||||
uint32_t cur_id = get_new_texture_id();
|
||||
|
||||
// The size in bytes of the image's pixel data.
|
||||
size_t size_bytes = width * height * 4 * sizeof(uint8_t);
|
||||
swapped_image_bytes.resize(size_bytes);
|
||||
|
||||
// Byteswap copy the pixel data.
|
||||
for (size_t i = 0; i < size_bytes; i++) {
|
||||
swapped_image_bytes[i] = MEM_B(i, data_in);
|
||||
}
|
||||
|
||||
// Create a texture name from the ID and queue its bytes.
|
||||
std::string texture_name = get_texture_name(cur_id);
|
||||
recompui::queue_image_from_bytes_rgba32(texture_name, swapped_image_bytes, width, height);
|
||||
|
||||
// Return the new texture ID.
|
||||
_return(ctx, cur_id);
|
||||
}
|
||||
|
||||
void recompui_create_texture_image_bytes(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) data_in = _arg<0, PTR(void)>(rdram, ctx);
|
||||
uint32_t size_bytes = _arg<1, u32>(rdram, ctx);
|
||||
uint32_t cur_id = get_new_texture_id();
|
||||
|
||||
// The size in bytes of the image's data.
|
||||
swapped_image_bytes.resize(size_bytes);
|
||||
|
||||
// Byteswap copy the image's data.
|
||||
for (size_t i = 0; i < size_bytes; i++) {
|
||||
swapped_image_bytes[i] = MEM_B(i, data_in);
|
||||
}
|
||||
|
||||
// Create a texture name from the ID and queue its bytes.
|
||||
std::string texture_name = get_texture_name(cur_id);
|
||||
recompui::queue_image_from_bytes_file(texture_name, swapped_image_bytes);
|
||||
|
||||
// Return the new texture ID.
|
||||
_return(ctx, cur_id);
|
||||
}
|
||||
|
||||
void recompui_destroy_texture(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint32_t texture_id = _arg<0, uint32_t>(rdram, ctx);
|
||||
|
||||
release_texture(texture_id);
|
||||
}
|
||||
|
||||
void recompui_create_imageview(uint8_t* rdram, recomp_context* ctx) {
|
||||
ContextId ui_context = get_context(rdram, ctx);
|
||||
Element* parent = arg_element<1>(rdram, ctx, ui_context);
|
||||
uint32_t texture_id = _arg<2, uint32_t>(rdram, ctx);
|
||||
|
||||
Element* ret = ui_context.create_element<Image>(parent, get_texture_name(texture_id));
|
||||
return_resource(ctx, ret->get_resource_id());
|
||||
}
|
||||
|
||||
void recompui_set_imageview_texture(uint8_t* rdram, recomp_context* ctx) {
|
||||
Style* resource = arg_style<0>(rdram, ctx);
|
||||
uint32_t texture_id = _arg<1, uint32_t>(rdram, ctx);
|
||||
|
||||
if (!resource->is_element()) {
|
||||
recompui::message_box("Fatal error in mod - attempted to set texture of non-element");
|
||||
assert(false);
|
||||
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||
}
|
||||
|
||||
Element* element = static_cast<Element*>(resource);
|
||||
element->set_src(get_texture_name(texture_id));
|
||||
}
|
||||
|
||||
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||
|
||||
void recompui::register_ui_image_exports() {
|
||||
REGISTER_FUNC(recompui_create_texture_rgba32);
|
||||
REGISTER_FUNC(recompui_create_texture_image_bytes);
|
||||
REGISTER_FUNC(recompui_destroy_texture);
|
||||
REGISTER_FUNC(recompui_create_imageview);
|
||||
REGISTER_FUNC(recompui_set_imageview_texture);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef __UI_API_IMAGES_H__
|
||||
#define __UI_API_IMAGES_H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace recompui {
|
||||
void register_ui_image_exports();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,190 +0,0 @@
|
||||
#include "ui_assign_players_modal.h"
|
||||
#include "elements/ui_label.h"
|
||||
#include "elements/ui_container.h"
|
||||
#include "recomp_ui.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
recompui::ContextId assign_players_modal_context;
|
||||
|
||||
static const float assignPlayersHFPaddingVert = 20.0f;
|
||||
static const float assignPlayersHFPaddingHorz = 20.0f;
|
||||
|
||||
static void set_button_side_styles(Element *el) {
|
||||
el->set_align_items(AlignItems::Center);
|
||||
el->set_width_auto();
|
||||
el->set_height_auto();
|
||||
el->set_flex_basis_auto();
|
||||
el->set_gap(8.0f);
|
||||
}
|
||||
|
||||
AssignPlayersModal::AssignPlayersModal(Element *parent) : Element(parent, 0, "div", false) {
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_background_color(theme::color::Transparent);
|
||||
set_display(Display::None);
|
||||
|
||||
Element* modal_overlay = context.create_element<Element>(this);
|
||||
modal_overlay->set_background_color(theme::color::BGOverlay);
|
||||
modal_overlay->set_position(Position::Absolute);
|
||||
modal_overlay->set_top(0);
|
||||
modal_overlay->set_right(0);
|
||||
modal_overlay->set_bottom(0);
|
||||
modal_overlay->set_left(0);
|
||||
|
||||
Element* modal_whole_page_wrapper = context.create_element<Element>(this);
|
||||
modal_whole_page_wrapper->set_display(Display::Flex);
|
||||
modal_whole_page_wrapper->set_position(Position::Absolute);
|
||||
modal_whole_page_wrapper->set_top(0);
|
||||
modal_whole_page_wrapper->set_right(0);
|
||||
modal_whole_page_wrapper->set_bottom(0);
|
||||
modal_whole_page_wrapper->set_left(0);
|
||||
modal_whole_page_wrapper->set_align_items(AlignItems::Center);
|
||||
modal_whole_page_wrapper->set_justify_content(JustifyContent::Center);
|
||||
|
||||
Element* modal = context.create_element<Element>(modal_whole_page_wrapper);
|
||||
modal->set_display(Display::Flex);
|
||||
modal->set_position(Position::Relative);
|
||||
modal->set_flex(1.0f, 1.0f);
|
||||
modal->set_flex_basis(100, Unit::Percent);
|
||||
modal->set_flex_direction(FlexDirection::Column);
|
||||
modal->set_width(100, Unit::Percent);
|
||||
modal->set_max_width(700, Unit::Dp);
|
||||
modal->set_height_auto();
|
||||
modal->set_margin_auto();
|
||||
modal->set_border_width(theme::border::width, Unit::Dp);
|
||||
modal->set_border_radius(theme::border::radius_lg, Unit::Dp);
|
||||
modal->set_border_color(theme::color::WhiteA20);
|
||||
modal->set_background_color(theme::color::ModalOverlay);
|
||||
|
||||
fake_focus_button = context.create_element<Element>(modal, 0, "button", false);
|
||||
fake_focus_button->set_position(Position::Absolute);
|
||||
fake_focus_button->set_width(0, Unit::Dp);
|
||||
fake_focus_button->set_height(0, Unit::Dp);
|
||||
fake_focus_button->set_opacity(0);
|
||||
|
||||
context.create_element<Label>(modal, "Assign Players", LabelStyle::Large);
|
||||
|
||||
player_elements_wrapper = context.create_element<Element>(modal, 0, "div", false);
|
||||
player_elements_wrapper->set_display(Display::Flex);
|
||||
player_elements_wrapper->set_flex_direction(FlexDirection::Row);
|
||||
player_elements_wrapper->set_justify_content(JustifyContent::SpaceBetween);
|
||||
player_elements_wrapper->set_align_items(AlignItems::Center);
|
||||
player_elements_wrapper->set_width(100, Unit::Percent);
|
||||
player_elements_wrapper->set_padding(24, Unit::Dp);
|
||||
|
||||
Element* footer = context.create_element<Element>(modal, 0, "div", false);
|
||||
footer->set_display(Display::Flex);
|
||||
footer->set_position(Position::Relative);
|
||||
footer->set_flex_direction(FlexDirection::Row);
|
||||
footer->set_align_items(AlignItems::Center);
|
||||
footer->set_justify_content(JustifyContent::SpaceBetween);
|
||||
footer->set_width(100.0f, Unit::Percent);
|
||||
footer->set_height_auto();
|
||||
|
||||
footer->set_padding_top(assignPlayersHFPaddingVert);
|
||||
footer->set_padding_bottom(assignPlayersHFPaddingVert);
|
||||
footer->set_padding_left(assignPlayersHFPaddingHorz);
|
||||
footer->set_padding_right(assignPlayersHFPaddingHorz);
|
||||
|
||||
auto left = context.create_element<Container>(footer, FlexDirection::Row, JustifyContent::FlexStart, 0);
|
||||
set_button_side_styles(left);
|
||||
close_button = context.create_element<Button>(left, "Cancel", ButtonStyle::Tertiary);
|
||||
close_button->add_pressed_callback(recompinput::stop_player_assignment_and_close_modal);
|
||||
|
||||
auto right = context.create_element<Container>(footer, FlexDirection::Row, JustifyContent::FlexEnd, 0);
|
||||
retry_button = context.create_element<Button>(right, "Retry", ButtonStyle::Warning);
|
||||
retry_button->set_enabled(false);
|
||||
retry_button->add_pressed_callback(recompinput::start_player_assignment);
|
||||
|
||||
confirm_button = context.create_element<Button>(right, "Confirm", ButtonStyle::Primary);
|
||||
confirm_button->set_enabled(false);
|
||||
confirm_button->add_pressed_callback(recompinput::commit_player_assignment);
|
||||
set_button_side_styles(right);
|
||||
}
|
||||
|
||||
AssignPlayersModal::~AssignPlayersModal() {
|
||||
}
|
||||
|
||||
void AssignPlayersModal::process_event(const Event &e) {
|
||||
if (!is_open) {
|
||||
return;
|
||||
}
|
||||
if (e.type == EventType::Update) {
|
||||
if (player_elements.empty() || player_elements.size() != recompinput::get_num_players()) {
|
||||
create_player_elements();
|
||||
}
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
player_elements[i]->update_assignment_player_card();
|
||||
}
|
||||
|
||||
if (!recompinput::is_player_assignment_active()) {
|
||||
if (recompinput::get_player_is_assigned(0)) {
|
||||
confirm_button->set_enabled(true);
|
||||
retry_button->set_enabled(true);
|
||||
if (was_assigning) {
|
||||
confirm_button->focus();
|
||||
}
|
||||
}
|
||||
|
||||
was_assigning = false;
|
||||
} else {
|
||||
fake_focus_button->focus();
|
||||
was_assigning = true;
|
||||
}
|
||||
|
||||
if (recompinput::get_player_is_assigned(0)) {
|
||||
confirm_button->set_enabled(true);
|
||||
retry_button->set_enabled(true);
|
||||
} else {
|
||||
confirm_button->set_enabled(false);
|
||||
retry_button->set_enabled(false);
|
||||
}
|
||||
|
||||
queue_update();
|
||||
}
|
||||
}
|
||||
|
||||
void AssignPlayersModal::create_player_elements() {
|
||||
player_elements_wrapper->clear_children();
|
||||
player_elements.clear();
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
for (int i = 0; i < recompinput::get_num_players(); i++) {
|
||||
PlayerCard* player_element = context.create_element<PlayerCard>(player_elements_wrapper, i, true);
|
||||
player_elements.push_back(player_element);
|
||||
}
|
||||
}
|
||||
|
||||
void AssignPlayersModal::open() {
|
||||
if (!recompui::is_context_shown(assign_players_modal_context)) {
|
||||
recompui::show_context(assign_players_modal_context, "");
|
||||
}
|
||||
|
||||
is_open = true;
|
||||
set_display(Display::Block);
|
||||
create_player_elements();
|
||||
queue_update();
|
||||
}
|
||||
void AssignPlayersModal::close() {
|
||||
if (recompui::is_context_shown(assign_players_modal_context)) {
|
||||
set_display(Display::None);
|
||||
recompui::hide_context(assign_players_modal_context);
|
||||
}
|
||||
|
||||
is_open = false;
|
||||
}
|
||||
|
||||
recompui::AssignPlayersModal *assign_players_modal = nullptr;
|
||||
|
||||
void init_assign_players_modal() {
|
||||
assign_players_modal_context = recompui::create_context();
|
||||
assign_players_modal_context.open();
|
||||
assign_players_modal = assign_players_modal_context.create_element<AssignPlayersModal>(assign_players_modal_context.get_root_element());
|
||||
assign_players_modal_context.close();
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_svg.h"
|
||||
#include "elements/ui_button.h"
|
||||
#include "ui_player_card.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class AssignPlayersModal : public Element {
|
||||
protected:
|
||||
bool is_open = false;
|
||||
bool was_assigning = false;
|
||||
Element* player_elements_wrapper = nullptr;
|
||||
Element* fake_focus_button = nullptr;
|
||||
std::vector<PlayerCard*> player_elements = {};
|
||||
|
||||
Button* close_button = nullptr;
|
||||
Button* retry_button = nullptr;
|
||||
Button* confirm_button = nullptr;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "AssignPlayersModal"; }
|
||||
private:
|
||||
void create_player_elements();
|
||||
public:
|
||||
AssignPlayersModal(Element *parent);
|
||||
virtual ~AssignPlayersModal();
|
||||
void open();
|
||||
void close();
|
||||
};
|
||||
|
||||
extern AssignPlayersModal *assign_players_modal;
|
||||
|
||||
void init_assign_players_modal();
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,32 +0,0 @@
|
||||
#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
|
||||
@@ -1,19 +0,0 @@
|
||||
#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
|
||||
@@ -1,181 +0,0 @@
|
||||
|
||||
#include "RmlUi/Core.h"
|
||||
#include "RmlUi/../../Source/Core/PropertyParserColour.h"
|
||||
#include "recomp_ui.h"
|
||||
#include <string.h>
|
||||
|
||||
using ColourMap = Rml::UnorderedMap<Rml::String, Rml::Colourb>;
|
||||
|
||||
namespace recompui {
|
||||
class PropertyParserColorHack : public Rml::PropertyParser {
|
||||
public:
|
||||
PropertyParserColorHack();
|
||||
virtual ~PropertyParserColorHack();
|
||||
bool ParseValue(Rml::Property& property, const Rml::String& value, const Rml::ParameterMap& /*parameters*/) const override;
|
||||
private:
|
||||
static ColourMap html_colours;
|
||||
};
|
||||
static_assert(sizeof(PropertyParserColorHack) == sizeof(Rml::PropertyParserColour));
|
||||
PropertyParserColorHack::PropertyParserColorHack() {
|
||||
html_colours["black"] = Rml::Colourb(0, 0, 0);
|
||||
html_colours["silver"] = Rml::Colourb(192, 192, 192);
|
||||
html_colours["gray"] = Rml::Colourb(128, 128, 128);
|
||||
html_colours["grey"] = Rml::Colourb(128, 128, 128);
|
||||
html_colours["white"] = Rml::Colourb(255, 255, 255);
|
||||
html_colours["maroon"] = Rml::Colourb(128, 0, 0);
|
||||
html_colours["red"] = Rml::Colourb(255, 0, 0);
|
||||
html_colours["orange"] = Rml::Colourb(255, 165, 0);
|
||||
html_colours["purple"] = Rml::Colourb(128, 0, 128);
|
||||
html_colours["fuchsia"] = Rml::Colourb(255, 0, 255);
|
||||
html_colours["green"] = Rml::Colourb(0, 128, 0);
|
||||
html_colours["lime"] = Rml::Colourb(0, 255, 0);
|
||||
html_colours["olive"] = Rml::Colourb(128, 128, 0);
|
||||
html_colours["yellow"] = Rml::Colourb(255, 255, 0);
|
||||
html_colours["navy"] = Rml::Colourb(0, 0, 128);
|
||||
html_colours["blue"] = Rml::Colourb(0, 0, 255);
|
||||
html_colours["teal"] = Rml::Colourb(0, 128, 128);
|
||||
html_colours["aqua"] = Rml::Colourb(0, 255, 255);
|
||||
html_colours["transparent"] = Rml::Colourb(0, 0, 0, 0);
|
||||
html_colours["whitesmoke"] = Rml::Colourb(245, 245, 245);
|
||||
|
||||
for (std::size_t i = 0; i < (std::size_t)recompui::theme::color::size; i++) {
|
||||
const char *color_name = recompui::theme::get_theme_color_name((recompui::theme::color)i);
|
||||
const recompui::Color color_value = recompui::theme::get_theme_color((recompui::theme::color)i);
|
||||
Rml::String color_name_lower = Rml::StringUtilities::ToLower(color_name);
|
||||
html_colours[color_name_lower] = Rml::Colourb(color_value.r, color_value.g, color_value.b, color_value.a);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyParserColorHack::~PropertyParserColorHack() {}
|
||||
|
||||
bool PropertyParserColorHack::ParseValue(Rml::Property& property, const Rml::String& value, const Rml::ParameterMap& /*parameters*/) const {
|
||||
if (value.empty())
|
||||
return false;
|
||||
|
||||
Rml::Colourb colour;
|
||||
|
||||
// Check for a hex colour.
|
||||
if (value[0] == '#')
|
||||
{
|
||||
char hex_values[4][2] = { {'f', 'f'}, {'f', 'f'}, {'f', 'f'}, {'f', 'f'} };
|
||||
|
||||
switch (value.size())
|
||||
{
|
||||
// Single hex digit per channel, RGB and alpha.
|
||||
case 5:
|
||||
hex_values[3][0] = hex_values[3][1] = value[4];
|
||||
//-fallthrough
|
||||
// Single hex digit per channel, RGB only.
|
||||
case 4:
|
||||
hex_values[0][0] = hex_values[0][1] = value[1];
|
||||
hex_values[1][0] = hex_values[1][1] = value[2];
|
||||
hex_values[2][0] = hex_values[2][1] = value[3];
|
||||
break;
|
||||
|
||||
// Two hex digits per channel, RGB and alpha.
|
||||
case 9:
|
||||
hex_values[3][0] = value[7];
|
||||
hex_values[3][1] = value[8];
|
||||
//-fallthrough
|
||||
// Two hex digits per channel, RGB only.
|
||||
case 7: memcpy(hex_values, &value.c_str()[1], sizeof(char) * 6); break;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
|
||||
// Parse each of the colour elements.
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
{
|
||||
int tens = Rml::Math::HexToDecimal(hex_values[i][0]);
|
||||
int ones = Rml::Math::HexToDecimal(hex_values[i][1]);
|
||||
if (tens == -1 || ones == -1)
|
||||
return false;
|
||||
|
||||
colour[i] = (Rml::byte)(tens * 16 + ones);
|
||||
}
|
||||
}
|
||||
else if (value.substr(0, 3) == "rgb")
|
||||
{
|
||||
Rml::StringList values;
|
||||
values.reserve(4);
|
||||
|
||||
size_t find = value.find('(');
|
||||
if (find == Rml::String::npos)
|
||||
return false;
|
||||
|
||||
size_t begin_values = find + 1;
|
||||
|
||||
Rml::StringUtilities::ExpandString(values, value.substr(begin_values, value.rfind(')') - begin_values), ',');
|
||||
|
||||
// Check if we're parsing an 'rgba' or 'rgb' colour declaration.
|
||||
if (value.size() > 3 && value[3] == 'a')
|
||||
{
|
||||
if (values.size() != 4)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (values.size() != 3)
|
||||
return false;
|
||||
|
||||
values.push_back("255");
|
||||
}
|
||||
|
||||
// Parse the three RGB values.
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
int component;
|
||||
|
||||
// We're parsing a percentage value.
|
||||
if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%')
|
||||
component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f));
|
||||
// We're parsing a 0 -> 255 integer value.
|
||||
else
|
||||
component = atoi(values[i].c_str());
|
||||
|
||||
colour[i] = (Rml::byte)(Rml::Math::Clamp(component, 0, 255));
|
||||
}
|
||||
// Parse the alpha value. Modified from the original RmlUi implementation to use 0-1 instead of 0-255.
|
||||
{
|
||||
int component;
|
||||
|
||||
// We're parsing a percentage value.
|
||||
if (values[3].size() > 0 && values[3][values[3].size() - 1] == '%')
|
||||
component = ((float)atof(values[3].substr(0, values[3].size() - 1).c_str()) * (255.0f / 100.0f));
|
||||
// We're parsing a 0 -> 1 float value.
|
||||
else
|
||||
component = atof(values[3].c_str()) * 255.0f;
|
||||
|
||||
colour[3] = (Rml::byte)(Rml::Math::Clamp(component, 0, 255));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for the specification of an HTML colour.
|
||||
ColourMap::const_iterator iterator = html_colours.find(Rml::StringUtilities::ToLower(value));
|
||||
if (iterator == html_colours.end())
|
||||
return false;
|
||||
else
|
||||
colour = (*iterator).second;
|
||||
}
|
||||
|
||||
property.value = Rml::Variant(colour);
|
||||
property.unit = Rml::Unit::COLOUR;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This hack overwrites the contents of a property parser pointer for "color" (which is known to point to a valid Rml::PropertyParserColour) with the contents of a PropertyParserColorHack.
|
||||
// This overwrites the object's vtable, allowing us to override color parsing behavior to use 0-1 alpha instead of 0-255.
|
||||
// Ideally we'd just replace the pointer itself, but RmlUi doesn't provide a way to do that currently.
|
||||
void apply_color_hack() {
|
||||
// Allocate and leak a parser to act as a vtable source.
|
||||
PropertyParserColorHack* new_parser = new PropertyParserColorHack();
|
||||
// Copy the allocated object into the color parser pointer to overwrite its vtable.
|
||||
memcpy((void*)Rml::StyleSheetSpecification::GetParser("color"), (void*)new_parser, sizeof(*new_parser));
|
||||
// TODO: Register the new parser with RmlUi when RmlUi supports custom parsers overrides.
|
||||
// Rml::StyleSheetSpecification::RegisterParser("color", new_parser);
|
||||
}
|
||||
|
||||
ColourMap PropertyParserColorHack::html_colours{};
|
||||
}
|
||||
@@ -1,747 +0,0 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "banjo_sound.h"
|
||||
#include "banjo_config.h"
|
||||
#include "banjo_debug.h"
|
||||
#include "banjo_render.h"
|
||||
#include "banjo_support.h"
|
||||
#include "promptfont.h"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "RmlUi/Core.h"
|
||||
|
||||
#include "core/ui_context.h"
|
||||
|
||||
ultramodern::renderer::GraphicsConfig new_options;
|
||||
Rml::DataModelHandle nav_help_model_handle;
|
||||
Rml::DataModelHandle general_model_handle;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
|
||||
int recompui::config_tab_to_index(recompui::ConfigTabId tab) {
|
||||
switch (tab) {
|
||||
case recompui::ConfigTabId::General:
|
||||
return 0;
|
||||
case recompui::ConfigTabId::Controls:
|
||||
return 1;
|
||||
case recompui::ConfigTabId::Graphics:
|
||||
return 2;
|
||||
case recompui::ConfigTabId::Sound:
|
||||
return 3;
|
||||
case recompui::ConfigTabId::Mods:
|
||||
return 4;
|
||||
case recompui::ConfigTabId::Debug:
|
||||
return 5;
|
||||
default:
|
||||
assert(false && "Unknown config tab.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void get_option(const T& input, Rml::Variant& output) {
|
||||
std::string value = "";
|
||||
to_json(value, input);
|
||||
|
||||
if (value.empty()) {
|
||||
throw std::runtime_error("Invalid value :" + std::to_string(int(input)));
|
||||
}
|
||||
|
||||
output = value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void set_option(T& output, const Rml::Variant& input) {
|
||||
T value = T::OptionCount;
|
||||
from_json(input.Get<std::string>(), value);
|
||||
|
||||
if (value == T::OptionCount) {
|
||||
throw std::runtime_error("Invalid value :" + input.Get<std::string>());
|
||||
}
|
||||
|
||||
output = value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void bind_option(Rml::DataModelConstructor& constructor, const std::string& name, T* option) {
|
||||
constructor.BindFunc(name,
|
||||
[option](Rml::Variant& out) { get_option(*option, out); },
|
||||
[option](const Rml::Variant& in) {
|
||||
set_option(*option, in);
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
graphics_model_handle.DirtyVariable("ds_info");
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void bind_atomic(Rml::DataModelConstructor& constructor, Rml::DataModelHandle handle, const char* name, std::atomic<T>* atomic_val) {
|
||||
constructor.BindFunc(name,
|
||||
[atomic_val](Rml::Variant& out) {
|
||||
out = atomic_val->load();
|
||||
},
|
||||
[atomic_val, handle, name](const Rml::Variant& in) mutable {
|
||||
atomic_val->store(in.Get<T>());
|
||||
handle.DirtyVariable(name);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static int focused_config_option_index = -1;
|
||||
|
||||
static bool msaa2x_supported = false;
|
||||
static bool msaa4x_supported = false;
|
||||
static bool msaa8x_supported = false;
|
||||
static bool sample_positions_supported = false;
|
||||
|
||||
static bool cont_active = true;
|
||||
|
||||
static recomp::InputDevice cur_device = recomp::InputDevice::Controller;
|
||||
|
||||
void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
|
||||
if (cont_active != cont_interacted) {
|
||||
cont_active = cont_interacted;
|
||||
|
||||
if (nav_help_model_handle) {
|
||||
nav_help_model_handle.DirtyVariable("nav_help__navigate");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__accept");
|
||||
nav_help_model_handle.DirtyVariable("nav_help__exit");
|
||||
}
|
||||
|
||||
if (graphics_model_handle) {
|
||||
graphics_model_handle.DirtyVariable("gfx_help__apply");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void close_config_menu_impl() {
|
||||
banjo::save_config();
|
||||
|
||||
recompui::ContextId config_context = recompui::get_config_context_id();
|
||||
recompui::ContextId sub_menu_context = recompui::get_config_sub_menu_context_id();
|
||||
|
||||
if (recompui::is_context_shown(sub_menu_context)) {
|
||||
recompui::hide_context(sub_menu_context);
|
||||
}
|
||||
else {
|
||||
recompui::hide_context(config_context);
|
||||
}
|
||||
|
||||
if (!ultramodern::is_game_started()) {
|
||||
recompui::show_context(recompui::get_launcher_context_id(), "");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove once RT64 gets native fullscreen support on Linux
|
||||
#if defined(__linux__)
|
||||
extern SDL_Window* window;
|
||||
#endif
|
||||
|
||||
void apply_graphics_config(void) {
|
||||
ultramodern::renderer::set_graphics_config(new_options);
|
||||
#if defined(__linux__) // TODO: Remove once RT64 gets native fullscreen support on Linux
|
||||
if (new_options.wm_option == ultramodern::renderer::WindowMode::Fullscreen) {
|
||||
SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
} else {
|
||||
SDL_SetWindowFullscreen(window,0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void close_config_menu() {
|
||||
if (ultramodern::renderer::get_graphics_config() != new_options) {
|
||||
recompui::open_choice_prompt(
|
||||
"Graphics options have changed",
|
||||
"Would you like to apply or discard the changes?",
|
||||
"Apply",
|
||||
"Discard",
|
||||
[]() {
|
||||
apply_graphics_config();
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
close_config_menu_impl();
|
||||
},
|
||||
[]() {
|
||||
new_options = ultramodern::renderer::get_graphics_config();
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
close_config_menu_impl();
|
||||
},
|
||||
recompui::ButtonStyle::Success,
|
||||
recompui::ButtonStyle::Danger,
|
||||
true,
|
||||
"config__close-menu-button"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
close_config_menu_impl();
|
||||
}
|
||||
|
||||
void banjo::open_quit_game_prompt() {
|
||||
recompui::open_choice_prompt(
|
||||
"Are you sure you want to quit?",
|
||||
"Any progress since your last save will be lost.",
|
||||
"Quit",
|
||||
"Cancel",
|
||||
[]() {
|
||||
ultramodern::quit();
|
||||
},
|
||||
[]() {},
|
||||
recompui::ButtonStyle::Danger,
|
||||
recompui::ButtonStyle::Tertiary,
|
||||
true,
|
||||
"config__quit-game-button"
|
||||
);
|
||||
}
|
||||
|
||||
// These defaults values don't matter, as the config file handling overrides them.
|
||||
struct ControlOptionsContext {
|
||||
int rumble_strength; // 0 to 100
|
||||
int gyro_sensitivity; // 0 to 100
|
||||
int mouse_sensitivity; // 0 to 100
|
||||
int joystick_deadzone; // 0 to 100
|
||||
recomp::BackgroundInputMode background_input_mode;
|
||||
banjo::CameraInvertMode camera_invert_mode;
|
||||
banjo::AnalogCamMode analog_cam_mode;
|
||||
banjo::CameraInvertMode analog_camera_invert_mode;
|
||||
};
|
||||
|
||||
ControlOptionsContext control_options_context;
|
||||
|
||||
int recomp::get_rumble_strength() {
|
||||
return control_options_context.rumble_strength;
|
||||
}
|
||||
|
||||
void recomp::set_rumble_strength(int strength) {
|
||||
control_options_context.rumble_strength = strength;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("rumble_strength");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_gyro_sensitivity() {
|
||||
return control_options_context.gyro_sensitivity;
|
||||
}
|
||||
|
||||
int recomp::get_mouse_sensitivity() {
|
||||
return control_options_context.mouse_sensitivity;
|
||||
}
|
||||
|
||||
int recomp::get_joystick_deadzone() {
|
||||
return control_options_context.joystick_deadzone;
|
||||
}
|
||||
|
||||
void recomp::set_gyro_sensitivity(int sensitivity) {
|
||||
control_options_context.gyro_sensitivity = sensitivity;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("gyro_sensitivity");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_mouse_sensitivity(int sensitivity) {
|
||||
control_options_context.mouse_sensitivity = sensitivity;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("mouse_sensitivity");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_joystick_deadzone(int deadzone) {
|
||||
control_options_context.joystick_deadzone = deadzone;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("joystick_deadzone");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::BackgroundInputMode recomp::get_background_input_mode() {
|
||||
return control_options_context.background_input_mode;
|
||||
}
|
||||
|
||||
void recomp::set_background_input_mode(recomp::BackgroundInputMode mode) {
|
||||
control_options_context.background_input_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("background_input_mode");
|
||||
}
|
||||
SDL_SetHint(
|
||||
SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
|
||||
mode == recomp::BackgroundInputMode::On
|
||||
? "1"
|
||||
: "0"
|
||||
);
|
||||
}
|
||||
|
||||
banjo::CameraInvertMode banjo::get_camera_invert_mode() {
|
||||
return control_options_context.camera_invert_mode;
|
||||
}
|
||||
|
||||
void banjo::set_camera_invert_mode(banjo::CameraInvertMode mode) {
|
||||
control_options_context.camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("camera_invert_mode");
|
||||
}
|
||||
}
|
||||
|
||||
banjo::AnalogCamMode banjo::get_analog_cam_mode() {
|
||||
return control_options_context.analog_cam_mode;
|
||||
}
|
||||
|
||||
void banjo::set_analog_cam_mode(banjo::AnalogCamMode mode) {
|
||||
control_options_context.analog_cam_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_cam_mode");
|
||||
}
|
||||
}
|
||||
|
||||
banjo::CameraInvertMode banjo::get_analog_camera_invert_mode() {
|
||||
return control_options_context.analog_camera_invert_mode;
|
||||
}
|
||||
|
||||
void banjo::set_analog_camera_invert_mode(banjo::CameraInvertMode mode) {
|
||||
control_options_context.analog_camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_camera_invert_mode");
|
||||
}
|
||||
}
|
||||
|
||||
struct SoundOptionsContext {
|
||||
std::atomic<int> main_volume; // Option to control the volume of all sound
|
||||
std::atomic<int> bgm_volume;
|
||||
std::atomic<int> low_health_beeps_enabled; // RmlUi doesn't seem to like "true"/"false" strings for setting variants so an int is used here instead.
|
||||
void reset() {
|
||||
bgm_volume = 100;
|
||||
main_volume = 100;
|
||||
low_health_beeps_enabled = (int)true;
|
||||
}
|
||||
SoundOptionsContext() {
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
SoundOptionsContext sound_options_context;
|
||||
|
||||
void banjo::reset_sound_settings() {
|
||||
sound_options_context.reset();
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyAllVariables();
|
||||
}
|
||||
}
|
||||
|
||||
void banjo::set_main_volume(int volume) {
|
||||
sound_options_context.main_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("main_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int banjo::get_main_volume() {
|
||||
return sound_options_context.main_volume.load();
|
||||
}
|
||||
|
||||
void banjo::set_bgm_volume(int volume) {
|
||||
sound_options_context.bgm_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("bgm_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int banjo::get_bgm_volume() {
|
||||
return sound_options_context.bgm_volume.load();
|
||||
}
|
||||
|
||||
struct DebugContext {
|
||||
Rml::DataModelHandle model_handle;
|
||||
bool debug_enabled = false;
|
||||
|
||||
DebugContext() {
|
||||
}
|
||||
};
|
||||
|
||||
DebugContext debug_context;
|
||||
|
||||
recompui::ContextId config_context;
|
||||
|
||||
recompui::ContextId recompui::get_config_context_id() {
|
||||
return config_context;
|
||||
}
|
||||
|
||||
// Helper copied from RmlUi to get a named child.
|
||||
Rml::Element* recompui::get_child_by_tag(Rml::Element* parent, const std::string& tag)
|
||||
{
|
||||
// Look for the existing child
|
||||
for (int i = 0; i < parent->GetNumChildren(); i++)
|
||||
{
|
||||
Rml::Element* child = parent->GetChild(i);
|
||||
if (child->GetTagName() == tag)
|
||||
return child;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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::ConfigTabId::Mods));
|
||||
if (in_mod_tab) {
|
||||
recompui::set_config_tabset_mod_nav();
|
||||
}
|
||||
else {
|
||||
Rml::ElementTabSet* tabset = recompui::get_config_tabset();
|
||||
Rml::Element* tabs = recompui::get_child_by_tag(tabset, "tabs");
|
||||
if (tabs != nullptr) {
|
||||
size_t num_children = tabs->GetNumChildren();
|
||||
for (size_t i = 0; i < num_children; i++) {
|
||||
tabs->GetChild(i)->SetProperty(Rml::PropertyId::NavDown, Rml::Style::Nav::Auto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ConfigMenu : public recompui::MenuController {
|
||||
private:
|
||||
ConfigTabsetListener config_tabset_listener;
|
||||
public:
|
||||
ConfigMenu() {
|
||||
|
||||
}
|
||||
~ConfigMenu() override {
|
||||
|
||||
}
|
||||
void load_document() override {
|
||||
config_context = recompui::create_context(banjo::get_asset_path("config_menu.rml"));
|
||||
recompui::update_mod_list(false);
|
||||
recompui::get_config_tabset()->AddEventListener(Rml::EventId::Tabchange, &config_tabset_listener);
|
||||
}
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "apply_options",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
apply_graphics_config();
|
||||
});
|
||||
recompui::register_event(listener, "config_keydown",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (!recompui::is_prompt_open() && event.GetId() == Rml::EventId::Keydown) {
|
||||
auto key = event.GetParameter<Rml::Input::KeyIdentifier>("key_identifier", Rml::Input::KeyIdentifier::KI_UNKNOWN);
|
||||
switch (key) {
|
||||
case Rml::Input::KeyIdentifier::KI_ESCAPE:
|
||||
close_config_menu();
|
||||
break;
|
||||
case Rml::Input::KeyIdentifier::KI_F:
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
apply_graphics_config();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
// This needs to be separate from `close_config_menu` so it ensures that the event is only on the target
|
||||
recompui::register_event(listener, "close_config_menu_backdrop",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||
close_config_menu();
|
||||
}
|
||||
});
|
||||
recompui::register_event(listener, "close_config_menu",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
close_config_menu();
|
||||
});
|
||||
|
||||
recompui::register_event(listener, "open_quit_game_prompt",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
banjo::open_quit_game_prompt();
|
||||
});
|
||||
}
|
||||
|
||||
void bind_config_list_events(Rml::DataModelConstructor &constructor) {
|
||||
constructor.BindEventCallback("set_cur_config_index",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
int option_index = inputs.at(0).Get<size_t>();
|
||||
// watch for mouseout being overzealous during event bubbling, only clear if the event's attached element matches the current
|
||||
if (option_index == -1 && event.GetType() == "mouseout" && event.GetCurrentElement() != event.GetTargetElement()) {
|
||||
return;
|
||||
}
|
||||
focused_config_option_index = option_index;
|
||||
model_handle.DirtyVariable("cur_config_index");
|
||||
});
|
||||
|
||||
constructor.Bind("cur_config_index", &focused_config_option_index);
|
||||
}
|
||||
|
||||
void make_graphics_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("graphics_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for the graphics config menu");
|
||||
}
|
||||
|
||||
ultramodern::sleep_milliseconds(50);
|
||||
new_options = ultramodern::renderer::get_graphics_config();
|
||||
bind_config_list_events(constructor);
|
||||
|
||||
constructor.BindFunc("res_option",
|
||||
[](Rml::Variant& out) { get_option(new_options.res_option, out); },
|
||||
[](const Rml::Variant& in) {
|
||||
set_option(new_options.res_option, in);
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
graphics_model_handle.DirtyVariable("ds_info");
|
||||
graphics_model_handle.DirtyVariable("ds_option");
|
||||
}
|
||||
);
|
||||
bind_option(constructor, "wm_option", &new_options.wm_option);
|
||||
bind_option(constructor, "ar_option", &new_options.ar_option);
|
||||
bind_option(constructor, "hr_option", &new_options.hr_option);
|
||||
bind_option(constructor, "msaa_option", &new_options.msaa_option);
|
||||
bind_option(constructor, "rr_option", &new_options.rr_option);
|
||||
constructor.BindFunc("rr_manual_value",
|
||||
[](Rml::Variant& out) {
|
||||
out = new_options.rr_manual_value;
|
||||
},
|
||||
[](const Rml::Variant& in) {
|
||||
new_options.rr_manual_value = in.Get<int>();
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
});
|
||||
constructor.BindFunc("ds_option",
|
||||
[](Rml::Variant& out) {
|
||||
if (new_options.res_option == ultramodern::renderer::Resolution::Auto) {
|
||||
out = 1;
|
||||
} else {
|
||||
out = new_options.ds_option;
|
||||
}
|
||||
},
|
||||
[](const Rml::Variant& in) {
|
||||
new_options.ds_option = in.Get<int>();
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
graphics_model_handle.DirtyVariable("ds_info");
|
||||
});
|
||||
|
||||
constructor.BindFunc("display_refresh_rate",
|
||||
[](Rml::Variant& out) {
|
||||
out = ultramodern::get_display_refresh_rate();
|
||||
});
|
||||
|
||||
constructor.BindFunc("options_changed",
|
||||
[](Rml::Variant& out) {
|
||||
out = (ultramodern::renderer::get_graphics_config() != new_options);
|
||||
});
|
||||
constructor.BindFunc("ds_info",
|
||||
[](Rml::Variant& out) {
|
||||
switch (new_options.res_option) {
|
||||
default:
|
||||
case ultramodern::renderer::Resolution::Auto:
|
||||
out = "Downsampling is not available at auto resolution";
|
||||
return;
|
||||
case ultramodern::renderer::Resolution::Original:
|
||||
if (new_options.ds_option == 2) {
|
||||
out = "Rendered in 480p and scaled to 240p";
|
||||
} else if (new_options.ds_option == 4) {
|
||||
out = "Rendered in 960p and scaled to 240p";
|
||||
}
|
||||
return;
|
||||
case ultramodern::renderer::Resolution::Original2x:
|
||||
if (new_options.ds_option == 2) {
|
||||
out = "Rendered in 960p and scaled to 480p";
|
||||
} else if (new_options.ds_option == 4) {
|
||||
out = "Rendered in 4K and scaled to 480p";
|
||||
}
|
||||
return;
|
||||
}
|
||||
out = "";
|
||||
});
|
||||
|
||||
constructor.BindFunc("gfx_help__apply", [](Rml::Variant& out) {
|
||||
if (cont_active) {
|
||||
// TODO: Needs the profile index.
|
||||
out = \
|
||||
(recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0).to_string() != "" ?
|
||||
" " + recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0).to_string() :
|
||||
""
|
||||
) + \
|
||||
(recomp::get_input_binding(1, recomp::GameInput::APPLY_MENU, 1).to_string() != "" ?
|
||||
" " + recomp::get_input_binding(1, recomp::GameInput::APPLY_MENU, 1).to_string() :
|
||||
""
|
||||
);
|
||||
} else {
|
||||
out = " " PF_KEYBOARD_F;
|
||||
}
|
||||
});
|
||||
|
||||
constructor.Bind("msaa2x_supported", &msaa2x_supported);
|
||||
constructor.Bind("msaa4x_supported", &msaa4x_supported);
|
||||
constructor.Bind("msaa8x_supported", &msaa8x_supported);
|
||||
constructor.Bind("sample_positions_supported", &sample_positions_supported);
|
||||
|
||||
graphics_model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_nav_help_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("nav_help_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for nav help");
|
||||
}
|
||||
|
||||
constructor.BindFunc("nav_help__navigate", [](Rml::Variant& out) {
|
||||
if (cont_active) {
|
||||
out = PF_DPAD;
|
||||
} else {
|
||||
out = PF_KEYBOARD_ARROWS PF_KEYBOARD_TAB;
|
||||
}
|
||||
});
|
||||
|
||||
constructor.BindFunc("nav_help__accept", [](Rml::Variant& out) {
|
||||
// TODO: Needs the profile index.
|
||||
if (cont_active) {
|
||||
out = \
|
||||
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0).to_string() + \
|
||||
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1).to_string();
|
||||
} else {
|
||||
out = PF_KEYBOARD_ENTER;
|
||||
}
|
||||
});
|
||||
|
||||
constructor.BindFunc("nav_help__exit", [](Rml::Variant& out) {
|
||||
// TODO: Needs the profile index.
|
||||
if (cont_active) {
|
||||
out = \
|
||||
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0).to_string() + \
|
||||
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1).to_string();
|
||||
} else {
|
||||
out = PF_KEYBOARD_ESCAPE;
|
||||
}
|
||||
});
|
||||
|
||||
nav_help_model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_general_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("general_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for the control options menu");
|
||||
}
|
||||
|
||||
bind_config_list_events(constructor);
|
||||
|
||||
constructor.Bind("rumble_strength", &control_options_context.rumble_strength);
|
||||
constructor.Bind("gyro_sensitivity", &control_options_context.gyro_sensitivity);
|
||||
constructor.Bind("mouse_sensitivity", &control_options_context.mouse_sensitivity);
|
||||
constructor.Bind("joystick_deadzone", &control_options_context.joystick_deadzone);
|
||||
bind_option(constructor, "background_input_mode", &control_options_context.background_input_mode);
|
||||
bind_option(constructor, "camera_invert_mode", &control_options_context.camera_invert_mode);
|
||||
bind_option(constructor, "analog_cam_mode", &control_options_context.analog_cam_mode);
|
||||
bind_option(constructor, "analog_camera_invert_mode", &control_options_context.analog_camera_invert_mode);
|
||||
|
||||
general_model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_sound_options_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("sound_options_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for the sound options menu");
|
||||
}
|
||||
|
||||
bind_config_list_events(constructor);
|
||||
|
||||
sound_options_model_handle = constructor.GetModelHandle();
|
||||
|
||||
bind_atomic(constructor, sound_options_model_handle, "main_volume", &sound_options_context.main_volume);
|
||||
bind_atomic(constructor, sound_options_model_handle, "bgm_volume", &sound_options_context.bgm_volume);
|
||||
bind_atomic(constructor, sound_options_model_handle, "low_health_beeps_enabled", &sound_options_context.low_health_beeps_enabled);
|
||||
}
|
||||
|
||||
void make_debug_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("debug_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for the debug menu");
|
||||
}
|
||||
|
||||
bind_config_list_events(constructor);
|
||||
|
||||
// Bind the debug mode enabled flag.
|
||||
constructor.Bind("debug_enabled", &debug_context.debug_enabled);
|
||||
|
||||
debug_context.model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_bindings(Rml::Context* context) override {
|
||||
// initially set cont state for ui help
|
||||
//recomp::config_menu_set_cont_or_kb(recompui::get_cont_active());
|
||||
make_nav_help_bindings(context);
|
||||
make_general_bindings(context);
|
||||
make_graphics_bindings(context);
|
||||
make_sound_options_bindings(context);
|
||||
make_debug_bindings(context);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<recompui::MenuController> recompui::create_config_menu() {
|
||||
return std::make_unique<ConfigMenu>();
|
||||
}
|
||||
|
||||
bool banjo::get_debug_mode_enabled() {
|
||||
return debug_context.debug_enabled;
|
||||
}
|
||||
|
||||
void banjo::set_debug_mode_enabled(bool enabled) {
|
||||
debug_context.debug_enabled = enabled;
|
||||
if (debug_context.model_handle) {
|
||||
debug_context.model_handle.DirtyVariable("debug_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
void recompui::update_supported_options() {
|
||||
msaa2x_supported = banjo::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
msaa4x_supported = banjo::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
msaa8x_supported = banjo::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
sample_positions_supported = banjo::renderer::RT64SamplePositionsSupported();
|
||||
|
||||
new_options = ultramodern::renderer::get_graphics_config();
|
||||
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
}
|
||||
|
||||
void recompui::toggle_fullscreen() {
|
||||
new_options.wm_option = (new_options.wm_option == ultramodern::renderer::WindowMode::Windowed) ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed;
|
||||
apply_graphics_config();
|
||||
graphics_model_handle.DirtyVariable("wm_option");
|
||||
}
|
||||
|
||||
void recompui::set_config_tab(ConfigTabId tab) {
|
||||
get_config_tabset()->SetActiveTab(config_tab_to_index(tab));
|
||||
}
|
||||
|
||||
Rml::ElementTabSet* recompui::get_config_tabset() {
|
||||
ContextId config_context = recompui::get_config_context_id();
|
||||
|
||||
ContextId old_context = recompui::try_close_current_context();
|
||||
|
||||
Rml::ElementDocument *doc = config_context.get_document();
|
||||
assert(doc != nullptr);
|
||||
|
||||
Rml::Element *tabset_el = doc->GetElementById("config_tabset");
|
||||
assert(tabset_el != nullptr);
|
||||
|
||||
Rml::ElementTabSet *tabset = rmlui_dynamic_cast<Rml::ElementTabSet *>(tabset_el);
|
||||
assert(tabset != nullptr);
|
||||
|
||||
if (old_context != ContextId::null()) {
|
||||
old_context.open();
|
||||
}
|
||||
|
||||
return tabset;
|
||||
}
|
||||
|
||||
Rml::Element* recompui::get_mod_tab() {
|
||||
ContextId config_context = recompui::get_config_context_id();
|
||||
|
||||
ContextId old_context = recompui::try_close_current_context();
|
||||
|
||||
Rml::ElementDocument* doc = config_context.get_document();
|
||||
assert(doc != nullptr);
|
||||
|
||||
Rml::Element* tab_el = doc->GetElementById("tab_mods");
|
||||
assert(tab_el != nullptr);
|
||||
|
||||
if (old_context != ContextId::null()) {
|
||||
old_context.open();
|
||||
}
|
||||
|
||||
return tab_el;
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
#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
|
||||
@@ -1,14 +0,0 @@
|
||||
#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
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "ui_config_page_controls_element.h"
|
||||
|
||||
// TODO: remove hardcoded recompinput funcs and data
|
||||
namespace recompinput {
|
||||
|
||||
}
|
||||
|
||||
namespace recompui {
|
||||
|
||||
static bool is_multiplayer_enabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#define DEFINE_INPUT(name, value, readable) GameInputContext{ readable, "Description for " #readable " input.", recompinput::GameInput::##name, !(recompinput::GameInput::##name == recompinput::GameInput::TOGGLE_MENU || recompinput::GameInput::##name == recompinput::GameInput::ACCEPT_MENU) },
|
||||
static std::vector<struct GameInputContext> temp_game_input_contexts = {
|
||||
DEFINE_ALL_INPUTS()
|
||||
};
|
||||
#undef DEFINE_INPUT
|
||||
|
||||
ElementConfigPageControls::ElementConfigPageControls(const Rml::String& tag) : Rml::Element(tag) {
|
||||
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
|
||||
SetProperty("width", "100%");
|
||||
SetProperty("height", "100%");
|
||||
|
||||
recompui::Element this_compat(this);
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
// controls_page = context.create_element<ConfigPageControls>(
|
||||
// &this_compat,
|
||||
// recompinput::get_num_players(),
|
||||
// temp_game_input_contexts
|
||||
// );
|
||||
}
|
||||
|
||||
ElementConfigPageControls::~ElementConfigPageControls() {
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "recomp_input.h"
|
||||
#include "elements/ui_config_page.h"
|
||||
#include "config/ui_config_page_controls.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ElementConfigPageControls : public Rml::Element {
|
||||
public:
|
||||
ElementConfigPageControls(const Rml::String& tag);
|
||||
virtual ~ElementConfigPageControls();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,65 +0,0 @@
|
||||
#include "ui_config_page_example.h"
|
||||
#include "elements/ui_button.h"
|
||||
#include "elements/ui_icon_button.h"
|
||||
#include "elements/ui_label.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
ElementConfigPageExample::ElementConfigPageExample(const Rml::String& tag) : Rml::Element(tag) {
|
||||
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
|
||||
SetProperty("width", "100%");
|
||||
SetProperty("height", "100%");
|
||||
|
||||
recompui::Element this_compat(this);
|
||||
recompui::ContextId context = get_current_context();
|
||||
|
||||
auto config_page = context.create_element<ConfigPage>(&this_compat);
|
||||
|
||||
auto body = config_page->get_body();
|
||||
{
|
||||
auto body_left = body->get_left();
|
||||
context.create_element<Label>(body_left, "First", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Second", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Third", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Fourth", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Fifth", LabelStyle::Normal);
|
||||
context.create_element<Label>(body_left, "Sixth", LabelStyle::Normal);
|
||||
}
|
||||
{
|
||||
auto body_right = body->get_right();
|
||||
auto right_p = context.create_element<recompui::Element>(body_right, 0, "p", true);
|
||||
right_p->set_text("Testing a really long string here that should probably wrap if its actually cool... but it might not be cool? im not sure. but we will need some extra formatting which im forgetting about right now so i guess it'll have to use set inner html or something like that idk.");
|
||||
}
|
||||
|
||||
auto header = config_page->add_header();
|
||||
{
|
||||
auto header_left = header->get_left();
|
||||
context.create_element<Button>(header_left, "Primary", ButtonStyle::Primary);
|
||||
context.create_element<Button>(header_left, "Secondary", ButtonStyle::Secondary);
|
||||
context.create_element<Button>(header_left, "Large", ButtonStyle::Primary, ButtonSize::Large);
|
||||
context.create_element<Button>(header_left, "Medium", ButtonStyle::Primary, ButtonSize::Medium);
|
||||
context.create_element<Button>(header_left, "Small", ButtonStyle::Primary, ButtonSize::Small);
|
||||
|
||||
}
|
||||
{
|
||||
auto header_right = header->get_right();
|
||||
context.create_element<IconButton>(header_right, "icons/Arrow.svg", ButtonStyle::Tertiary, IconButtonSize::Small);
|
||||
context.create_element<IconButton>(header_right, "icons/Reset.svg", ButtonStyle::Danger, IconButtonSize::XLarge);
|
||||
}
|
||||
|
||||
auto footer = config_page->add_footer();
|
||||
{
|
||||
auto footer_left = footer->get_left();
|
||||
context.create_element<Button>(footer_left, "Goodbye", ButtonStyle::Warning);
|
||||
}
|
||||
{
|
||||
auto footer_right = footer->get_right();
|
||||
context.create_element<Button>(footer_right, "Last button", ButtonStyle::Success);
|
||||
context.create_element<IconButton>(footer_right, "icons/X.svg", ButtonStyle::Basic, IconButtonSize::Medium);
|
||||
}
|
||||
}
|
||||
|
||||
ElementConfigPageExample::~ElementConfigPageExample() {
|
||||
}
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "elements/ui_config_page.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ElementConfigPageExample : public Rml::Element {
|
||||
public:
|
||||
ElementConfigPageExample(const Rml::String& tag);
|
||||
virtual ~ElementConfigPageExample();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
@@ -1,269 +0,0 @@
|
||||
#include "ui_config_sub_menu.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string_view>
|
||||
|
||||
#include "recomp_ui.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
// ConfigOptionElement
|
||||
|
||||
|
||||
void ConfigOptionElement::process_event(const Event &e) {
|
||||
switch (e.type) {
|
||||
case EventType::Hover:
|
||||
if (hover_callback == nullptr) {
|
||||
break;
|
||||
}
|
||||
hover_callback(this, std::get<EventHover>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigOptionElement::ConfigOptionElement(Element *parent) : Element(parent, Events(EventType::Hover)) {
|
||||
set_display(Display::Flex);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_gap(16.0f);
|
||||
set_height(100.0f);
|
||||
|
||||
name_label = get_current_context().create_element<Label>(this, LabelStyle::Normal);
|
||||
}
|
||||
|
||||
ConfigOptionElement::~ConfigOptionElement() {
|
||||
|
||||
}
|
||||
|
||||
void ConfigOptionElement::set_option_id(std::string_view id) {
|
||||
this->option_id = id;
|
||||
}
|
||||
|
||||
void ConfigOptionElement::set_name(std::string_view name) {
|
||||
this->name = name;
|
||||
name_label->set_text(name);
|
||||
}
|
||||
|
||||
void ConfigOptionElement::set_description(std::string_view description) {
|
||||
this->description = description;
|
||||
}
|
||||
|
||||
void ConfigOptionElement::set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback) {
|
||||
hover_callback = callback;
|
||||
}
|
||||
|
||||
void ConfigOptionElement::set_focus_callback(std::function<void(const std::string &, bool)> callback) {
|
||||
focus_callback = callback;
|
||||
}
|
||||
|
||||
const std::string &ConfigOptionElement::get_description() const {
|
||||
return description;
|
||||
}
|
||||
|
||||
// ConfigOptionSlider
|
||||
|
||||
void ConfigOptionSlider::slider_value_changed(double v) {
|
||||
callback(option_id, v);
|
||||
}
|
||||
|
||||
ConfigOptionSlider::ConfigOptionSlider(Element *parent, double value, double min_value, double max_value, double step_value, bool percent, std::function<void(const std::string &, double)> callback) : ConfigOptionElement(parent) {
|
||||
this->callback = callback;
|
||||
|
||||
slider = get_current_context().create_element<Slider>(this, percent ? SliderType::Percent : SliderType::Double);
|
||||
slider->set_max_width(380.0f);
|
||||
slider->set_min_value(min_value);
|
||||
slider->set_max_value(max_value);
|
||||
slider->set_step_value(step_value);
|
||||
slider->set_value(value);
|
||||
slider->add_value_changed_callback([this](double v){ slider_value_changed(v); });
|
||||
slider->set_focus_callback([this](bool active) {
|
||||
focus_callback(option_id, active);
|
||||
});
|
||||
}
|
||||
|
||||
// ConfigOptionTextInput
|
||||
|
||||
void ConfigOptionTextInput::text_changed(const std::string &text) {
|
||||
callback(option_id, text);
|
||||
}
|
||||
|
||||
ConfigOptionTextInput::ConfigOptionTextInput(Element *parent, std::string_view value, std::function<void(const std::string &, const std::string &)> callback) : ConfigOptionElement(parent) {
|
||||
this->callback = callback;
|
||||
|
||||
text_input = get_current_context().create_element<TextInput>(this);
|
||||
text_input->set_max_width(400.0f);
|
||||
text_input->set_text(value);
|
||||
text_input->add_text_changed_callback([this](const std::string &text){ text_changed(text); });
|
||||
text_input->set_focus_callback([this](bool active) {
|
||||
focus_callback(option_id, active);
|
||||
});
|
||||
}
|
||||
|
||||
// ConfigOptionRadio
|
||||
|
||||
void ConfigOptionRadio::index_changed(uint32_t index) {
|
||||
callback(option_id, index);
|
||||
}
|
||||
|
||||
ConfigOptionRadio::ConfigOptionRadio(Element *parent, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback) : ConfigOptionElement(parent) {
|
||||
this->callback = callback;
|
||||
|
||||
radio = get_current_context().create_element<Radio>(this);
|
||||
radio->set_focus_callback([this](bool active) {
|
||||
focus_callback(option_id, active);
|
||||
});
|
||||
radio->add_index_changed_callback([this](uint32_t index){ index_changed(index); });
|
||||
for (std::string_view option : options) {
|
||||
radio->add_option(option);
|
||||
}
|
||||
|
||||
if (value < options.size()) {
|
||||
radio->set_index(value);
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigSubMenu
|
||||
|
||||
void ConfigSubMenu::back_button_pressed() {
|
||||
// Hide the config sub menu and show the config menu.
|
||||
ContextId config_context = recompui::get_config_context_id();
|
||||
ContextId sub_menu_context = recompui::get_config_sub_menu_context_id();
|
||||
|
||||
recompui::hide_context(sub_menu_context);
|
||||
recompui::show_context(config_context, "");
|
||||
recompui::focus_mod_configure_button();
|
||||
}
|
||||
|
||||
void ConfigSubMenu::set_description_option_element(ConfigOptionElement *option, bool active) {
|
||||
if (active) {
|
||||
description_option_element = option;
|
||||
}
|
||||
else if (description_option_element == option) {
|
||||
description_option_element = nullptr;
|
||||
}
|
||||
|
||||
if (description_option_element == nullptr) {
|
||||
description_label->set_text("");
|
||||
}
|
||||
else {
|
||||
description_label->set_text(description_option_element->get_description());
|
||||
}
|
||||
}
|
||||
|
||||
ConfigSubMenu::ConfigSubMenu(Element *parent) : Element(parent) {
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex(1, 1, 100.0f, Unit::Percent);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
set_height(100.0f, Unit::Percent);
|
||||
|
||||
recompui::ContextId context = get_current_context();
|
||||
header_container = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::FlexStart);
|
||||
header_container->set_flex_grow(0.0f);
|
||||
header_container->set_align_items(AlignItems::Center);
|
||||
header_container->set_padding(12.0f);
|
||||
header_container->set_gap(24.0f);
|
||||
|
||||
{
|
||||
back_button = context.create_element<Button>(header_container, "Back", ButtonStyle::Secondary);
|
||||
back_button->add_pressed_callback([this](){ back_button_pressed(); });
|
||||
title_label = context.create_element<Label>(header_container, "Title", LabelStyle::Large);
|
||||
}
|
||||
|
||||
body_container = context.create_element<Container>(this, FlexDirection::Row, JustifyContent::SpaceEvenly);
|
||||
body_container->set_padding(32.0f);
|
||||
{
|
||||
config_container = context.create_element<Container>(body_container, FlexDirection::Column, JustifyContent::Center);
|
||||
config_container->set_display(Display::Block);
|
||||
config_container->set_flex_basis(100.0f);
|
||||
config_container->set_align_items(AlignItems::Center);
|
||||
{
|
||||
config_scroll_container = context.create_element<ScrollContainer>(config_container, ScrollDirection::Vertical);
|
||||
}
|
||||
|
||||
description_label = context.create_element<Label>(body_container, "", LabelStyle::Small);
|
||||
description_label->set_min_width(800.0f);
|
||||
description_label->set_padding_left(16.0f);
|
||||
description_label->set_padding_right(16.0f);
|
||||
}
|
||||
|
||||
recompui::get_current_context().set_autofocus_element(back_button);
|
||||
}
|
||||
|
||||
ConfigSubMenu::~ConfigSubMenu() {
|
||||
|
||||
}
|
||||
|
||||
void ConfigSubMenu::enter(std::string_view title) {
|
||||
title_label->set_text(title);
|
||||
}
|
||||
|
||||
void ConfigSubMenu::clear_options() {
|
||||
config_scroll_container->clear_children();
|
||||
config_option_elements.clear();
|
||||
description_option_element = nullptr;
|
||||
}
|
||||
|
||||
void ConfigSubMenu::add_option(ConfigOptionElement *option, std::string_view id, std::string_view name, std::string_view description) {
|
||||
option->set_option_id(id);
|
||||
option->set_name(name);
|
||||
option->set_description(description);
|
||||
option->set_hover_callback([this](ConfigOptionElement *option, bool active){ set_description_option_element(option, active); });
|
||||
option->set_focus_callback([this, option](const std::string &id, bool active) { set_description_option_element(option, active); });
|
||||
if (config_option_elements.empty()) {
|
||||
back_button->set_nav(NavDirection::Down, option->get_focus_element());
|
||||
option->set_nav(NavDirection::Up, back_button);
|
||||
}
|
||||
else {
|
||||
config_option_elements.back()->set_nav(NavDirection::Down, option->get_focus_element());
|
||||
option->set_nav(NavDirection::Up, config_option_elements.back()->get_focus_element());
|
||||
}
|
||||
|
||||
config_option_elements.emplace_back(option);
|
||||
}
|
||||
|
||||
void ConfigSubMenu::add_slider_option(std::string_view id, std::string_view name, std::string_view description, double value, double min, double max, double step, bool percent, std::function<void(const std::string &, double)> callback) {
|
||||
ConfigOptionSlider *option_slider = get_current_context().create_element<ConfigOptionSlider>(config_scroll_container, value, min, max, step, percent, callback);
|
||||
add_option(option_slider, id, name, description);
|
||||
}
|
||||
|
||||
void ConfigSubMenu::add_text_option(std::string_view id, std::string_view name, std::string_view description, std::string_view value, std::function<void(const std::string &, const std::string &)> callback) {
|
||||
ConfigOptionTextInput *option_text_input = get_current_context().create_element<ConfigOptionTextInput>(config_scroll_container, value, callback);
|
||||
add_option(option_text_input, id, name, description);
|
||||
}
|
||||
|
||||
void ConfigSubMenu::add_radio_option(std::string_view id, std::string_view name, std::string_view description, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback) {
|
||||
ConfigOptionRadio *option_radio = get_current_context().create_element<ConfigOptionRadio>(config_scroll_container, value, options, callback);
|
||||
add_option(option_radio, id, name, description);
|
||||
}
|
||||
|
||||
// ElementConfigSubMenu
|
||||
|
||||
ElementConfigSubMenu::ElementConfigSubMenu(const Rml::String &tag) : Rml::Element(tag) {
|
||||
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Flex);
|
||||
SetProperty("width", "100%");
|
||||
SetProperty("height", "100%");
|
||||
|
||||
recompui::Element this_compat(this);
|
||||
recompui::ContextId context = get_current_context();
|
||||
config_sub_menu = context.create_element<ConfigSubMenu>(&this_compat);
|
||||
}
|
||||
|
||||
ElementConfigSubMenu::~ElementConfigSubMenu() {
|
||||
|
||||
}
|
||||
|
||||
void ElementConfigSubMenu::set_display(bool display) {
|
||||
SetProperty(Rml::PropertyId::Display, display ? Rml::Style::Display::Block : Rml::Style::Display::None);
|
||||
}
|
||||
|
||||
ConfigSubMenu *ElementConfigSubMenu::get_config_sub_menu_element() const {
|
||||
return config_sub_menu;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
#ifndef RECOMPUI_CONFIG_SUB_MENU_H
|
||||
#define RECOMPUI_CONFIG_SUB_MENU_H
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "elements/ui_button.h"
|
||||
#include "elements/ui_container.h"
|
||||
#include "elements/ui_label.h"
|
||||
#include "elements/ui_radio.h"
|
||||
#include "elements/ui_scroll_container.h"
|
||||
#include "elements/ui_slider.h"
|
||||
#include "elements/ui_text_input.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ConfigOptionElement : public Element {
|
||||
protected:
|
||||
Label *name_label = nullptr;
|
||||
std::string option_id;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::function<void(ConfigOptionElement *, bool)> hover_callback = nullptr;
|
||||
std::function<void(const std::string &, bool)> focus_callback = nullptr;
|
||||
|
||||
virtual void process_event(const Event &e) override;
|
||||
std::string_view get_type_name() override { return "ConfigOptionElement"; }
|
||||
public:
|
||||
ConfigOptionElement(Element *parent);
|
||||
virtual ~ConfigOptionElement();
|
||||
void set_option_id(std::string_view id);
|
||||
void set_name(std::string_view name);
|
||||
void set_description(std::string_view description);
|
||||
void set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback);
|
||||
void set_focus_callback(std::function<void(const std::string &, bool)> callback);
|
||||
const std::string &get_description() const;
|
||||
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); }
|
||||
virtual Element* get_focus_element() { return this; }
|
||||
};
|
||||
|
||||
class ConfigOptionSlider : public ConfigOptionElement {
|
||||
protected:
|
||||
Slider *slider = nullptr;
|
||||
std::function<void(const std::string &, double)> callback;
|
||||
|
||||
void slider_value_changed(double v);
|
||||
std::string_view get_type_name() override { return "ConfigOptionSlider"; }
|
||||
public:
|
||||
ConfigOptionSlider(Element *parent, double value, double min_value, double max_value, double step_value, bool percent, std::function<void(const std::string &, double)> callback);
|
||||
Element* get_focus_element() override { return slider; }
|
||||
};
|
||||
|
||||
class ConfigOptionTextInput : public ConfigOptionElement {
|
||||
protected:
|
||||
TextInput *text_input = nullptr;
|
||||
std::function<void(const std::string &, const std::string &)> callback;
|
||||
|
||||
void text_changed(const std::string &text);
|
||||
std::string_view get_type_name() override { return "ConfigOptionTextInput"; }
|
||||
public:
|
||||
ConfigOptionTextInput(Element *parent, std::string_view value, std::function<void(const std::string &, const std::string &)> callback);
|
||||
Element* get_focus_element() override { return text_input; }
|
||||
};
|
||||
|
||||
class ConfigOptionRadio : public ConfigOptionElement {
|
||||
protected:
|
||||
Radio *radio = nullptr;
|
||||
std::function<void(const std::string &, uint32_t)> callback;
|
||||
|
||||
void index_changed(uint32_t index);
|
||||
std::string_view get_type_name() override { return "ConfigOptionRadio"; }
|
||||
public:
|
||||
ConfigOptionRadio(Element *parent, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback);
|
||||
Element* get_focus_element() override { return radio; }
|
||||
};
|
||||
|
||||
class ConfigSubMenu : public Element {
|
||||
private:
|
||||
Container *header_container = nullptr;
|
||||
Button *back_button = nullptr;
|
||||
Label *title_label = nullptr;
|
||||
Container *body_container = nullptr;
|
||||
Label *description_label = nullptr;
|
||||
Container *config_container = nullptr;
|
||||
ScrollContainer *config_scroll_container = nullptr;
|
||||
std::vector<ConfigOptionElement *> config_option_elements;
|
||||
ConfigOptionElement * description_option_element = nullptr;
|
||||
|
||||
void back_button_pressed();
|
||||
void set_description_option_element(ConfigOptionElement *option, bool active);
|
||||
void add_option(ConfigOptionElement *option, std::string_view id, std::string_view name, std::string_view description);
|
||||
protected:
|
||||
std::string_view get_type_name() override { return "ConfigSubMenu"; }
|
||||
public:
|
||||
ConfigSubMenu(Element *parent);
|
||||
virtual ~ConfigSubMenu();
|
||||
void enter(std::string_view title);
|
||||
void clear_options();
|
||||
void add_slider_option(std::string_view id, std::string_view name, std::string_view description, double value, double min, double max, double step, bool percent, std::function<void(const std::string &, double)> callback);
|
||||
void add_text_option(std::string_view id, std::string_view name, std::string_view description, std::string_view value, std::function<void(const std::string &, const std::string &)> callback);
|
||||
void add_radio_option(std::string_view id, std::string_view name, std::string_view description, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback);
|
||||
};
|
||||
|
||||
class ElementConfigSubMenu : public Rml::Element {
|
||||
public:
|
||||
ElementConfigSubMenu(const Rml::String &tag);
|
||||
virtual ~ElementConfigSubMenu();
|
||||
void set_display(bool display);
|
||||
ConfigSubMenu *get_config_sub_menu_element() const;
|
||||
private:
|
||||
ConfigSubMenu *config_sub_menu;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "ui_elements.h"
|
||||
|
||||
struct RecompCustomElement {
|
||||
Rml::String tag;
|
||||
std::unique_ptr<Rml::ElementInstancer> instancer;
|
||||
};
|
||||
|
||||
#define CUSTOM_ELEMENT(s, e) { s, std::make_unique< Rml::ElementInstancerGeneric< e > >() }
|
||||
|
||||
static RecompCustomElement custom_elements[] = {
|
||||
CUSTOM_ELEMENT("recomp-mod-menu", recompui::ElementModMenu),
|
||||
CUSTOM_ELEMENT("recomp-config-sub-menu", recompui::ElementConfigSubMenu),
|
||||
CUSTOM_ELEMENT("recomp-config-page-example", recompui::ElementConfigPageExample),
|
||||
CUSTOM_ELEMENT("recomp-config-page-controls", recompui::ElementConfigPageControls),
|
||||
CUSTOM_ELEMENT("recomp-color", recompui::ElementColor),
|
||||
};
|
||||
|
||||
void recompui::register_custom_elements() {
|
||||
for (auto& element_config : custom_elements) {
|
||||
Rml::Factory::RegisterElementInstancer(element_config.tag, element_config.instancer.get());
|
||||
}
|
||||
}
|
||||
|
||||
Rml::ElementInstancer* recompui::get_custom_element_instancer(std::string tag) {
|
||||
for (auto& element_config : custom_elements) {
|
||||
if (tag == element_config.tag) {
|
||||
return element_config.instancer.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rml::ElementPtr recompui::create_custom_element(Rml::Element* parent, std::string tag) {
|
||||
auto instancer = recompui::get_custom_element_instancer(tag);
|
||||
const Rml::XMLAttributes attributes = {};
|
||||
if (Rml::ElementPtr element = instancer->InstanceElement(parent, tag, attributes))
|
||||
{
|
||||
element->SetInstancer(instancer);
|
||||
element->SetAttributes(attributes);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef RECOMPUI_ELEMENTS_H
|
||||
#define RECOMPUI_ELEMENTS_H
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "RmlUi/Core/Element.h"
|
||||
|
||||
#include "ui_mod_menu.h"
|
||||
#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();
|
||||
|
||||
Rml::ElementInstancer* get_custom_element_instancer(std::string tag);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,154 +0,0 @@
|
||||
#ifndef __UI_HELPERS_H__
|
||||
#define __UI_HELPERS_H__
|
||||
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "librecomp/addresses.hpp"
|
||||
|
||||
#include "elements/ui_element.h"
|
||||
#include "elements/ui_types.h"
|
||||
#include "core/ui_context.h"
|
||||
#include "core/ui_resource.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
constexpr ResourceId root_element_id{ 0xFFFFFFFE };
|
||||
|
||||
inline ContextId get_context(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint32_t context_id = _arg<0, uint32_t>(rdram, ctx);
|
||||
return ContextId{ .slot_id = context_id };
|
||||
}
|
||||
|
||||
inline float arg_float2(uint8_t* rdram, recomp_context* ctx) {
|
||||
union {
|
||||
float f32;
|
||||
uint32_t u32;
|
||||
} val;
|
||||
|
||||
val.u32 = _arg<2, uint32_t>(rdram, ctx);
|
||||
return val.f32;
|
||||
}
|
||||
|
||||
inline float arg_float3(uint8_t* rdram, recomp_context* ctx) {
|
||||
union {
|
||||
float f32;
|
||||
uint32_t u32;
|
||||
} val;
|
||||
|
||||
val.u32 = _arg<3, uint32_t>(rdram, ctx);
|
||||
return val.f32;
|
||||
}
|
||||
|
||||
inline float arg_float4(uint8_t* rdram, recomp_context* ctx) {
|
||||
union {
|
||||
float f32;
|
||||
uint32_t u32;
|
||||
} val;
|
||||
|
||||
val.u32 = MEM_W(0x10, ctx->r29);
|
||||
return val.f32;
|
||||
}
|
||||
|
||||
inline float arg_float5(uint8_t* rdram, recomp_context* ctx) {
|
||||
union {
|
||||
float f32;
|
||||
uint32_t u32;
|
||||
} val;
|
||||
|
||||
val.u32 = MEM_W(0x14, ctx->r29);
|
||||
return val.f32;
|
||||
}
|
||||
|
||||
inline float arg_float6(uint8_t* rdram, recomp_context* ctx) {
|
||||
union {
|
||||
float f32;
|
||||
uint32_t u32;
|
||||
} val;
|
||||
|
||||
val.u32 = MEM_W(0x18, ctx->r29);
|
||||
return val.f32;
|
||||
}
|
||||
|
||||
template <int arg_index>
|
||||
ResourceId arg_resource_id(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint32_t slot_id = _arg<arg_index, uint32_t>(rdram, ctx);
|
||||
|
||||
return ResourceId{ .slot_id = slot_id };
|
||||
}
|
||||
|
||||
template <int arg_index>
|
||||
Element* arg_element(uint8_t* rdram, recomp_context* ctx, ContextId ui_context) {
|
||||
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
|
||||
|
||||
if (resource == ResourceId::null()) {
|
||||
return nullptr;
|
||||
}
|
||||
else if (resource == root_element_id) {
|
||||
return ui_context.get_root_element();
|
||||
}
|
||||
|
||||
return resource.as_element();
|
||||
}
|
||||
|
||||
template <int arg_index>
|
||||
Style* arg_style(uint8_t* rdram, recomp_context* ctx) {
|
||||
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
|
||||
|
||||
if (resource == ResourceId::null()) {
|
||||
return nullptr;
|
||||
}
|
||||
else if (resource == root_element_id) {
|
||||
ContextId ui_context = recompui::get_current_context();
|
||||
return ui_context.get_root_element();
|
||||
}
|
||||
|
||||
return *resource;
|
||||
}
|
||||
|
||||
template <int arg_index>
|
||||
Color arg_color(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(u8) color_arg = _arg<arg_index, PTR(u8)>(rdram, ctx);
|
||||
|
||||
Color ret{};
|
||||
|
||||
ret.r = MEM_B(0, color_arg);
|
||||
ret.g = MEM_B(1, color_arg);
|
||||
ret.b = MEM_B(2, color_arg);
|
||||
ret.a = MEM_B(3, color_arg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void return_resource(recomp_context* ctx, ResourceId resource) {
|
||||
_return<uint32_t>(ctx, resource.slot_id);
|
||||
}
|
||||
|
||||
inline void return_string(uint8_t* rdram, recomp_context* ctx, const std::string& ret) {
|
||||
gpr addr = (reinterpret_cast<uint8_t*>(recomp::alloc(rdram, ret.size() + 1)) - rdram) + 0xFFFFFFFF80000000ULL;
|
||||
|
||||
for (size_t i = 0; i < ret.size(); i++) {
|
||||
MEM_B(i, addr) = ret[i];
|
||||
}
|
||||
MEM_B(ret.size(), addr) = '\x00';
|
||||
|
||||
_return<PTR(char)>(ctx, addr);
|
||||
}
|
||||
|
||||
inline std::string decode_string(uint8_t* rdram, PTR(char) str) {
|
||||
// Get the length of the byteswapped string.
|
||||
size_t len = 0;
|
||||
while (MEM_B(str, len) != 0x00) {
|
||||
len++;
|
||||
}
|
||||
|
||||
std::string ret{};
|
||||
ret.reserve(len + 1);
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
ret += (char)MEM_B(str, i);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user