Add mod UI API exports for slider, password input, and label radio and expose RmlUi debugger on F8

This commit is contained in:
Mr-Wiseguy 2025-04-07 21:46:53 -04:00
parent 1c6f445503
commit d614634bf1
16 changed files with 283 additions and 32 deletions

10
include/overloaded.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef __OVERLOADED_H__
#define __OVERLOADED_H__
// Helper for std::visit
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
#endif

View file

@ -6,6 +6,7 @@
#define HLSL_CPU
#include "hle/rt64_application.h"
#include "rt64_render_hooks.h"
#include "overloaded.h"
#include "ultramodern/ultramodern.hpp"
#include "ultramodern/config.hpp"
@ -14,12 +15,6 @@
#include "recomp_ui.h"
#include "concurrentqueue.h"
// Helper class for variant visiting.
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
static bool sample_positions_supported = false;
static bool high_precision_fb_enabled = false;

View file

@ -6,7 +6,6 @@ namespace recompui {
Container::Container(Element *parent, FlexDirection direction, JustifyContent justify_content) : Element(parent) {
set_display(Display::Flex);
set_flex(1.0f, 1.0f);
set_flex_direction(direction);
set_justify_content(justify_content);
}

View file

@ -1,5 +1,6 @@
#include "RmlUi/Core/StringUtilities.h"
#include "overloaded.h"
#include "recomp_ui.h"
#include "ui_element.h"
#include "../core/ui_context.h"
@ -28,6 +29,9 @@ Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_clas
base = base_owning.get();
}
set_display(Display::Block);
set_property(Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox);
register_event_listeners(events_enabled);
}
@ -372,6 +376,39 @@ float Element::get_client_height() {
return base->GetClientHeight();
}
uint32_t Element::get_input_value_u32() {
ElementValue value = get_element_value();
return std::visit(overloaded {
[](double d) { return (uint32_t)d; },
[](float f) { return (uint32_t)f; },
[](uint32_t u) { return u; },
[](std::monostate) { return 0U; }
}, value);
}
float Element::get_input_value_float() {
ElementValue value = get_element_value();
return std::visit(overloaded {
[](double d) { return (float)d; },
[](float f) { return f; },
[](uint32_t u) { return (float)u; },
[](std::monostate) { return 0.0f; }
}, value);
}
double Element::get_input_value_double() {
ElementValue value = get_element_value();
return std::visit(overloaded {
[](double d) { return d; },
[](float f) { return (double)f; },
[](uint32_t u) { return (double)u; },
[](std::monostate) { return 0.0; }
}, value);
}
void Element::queue_update() {
ContextId cur_context = get_current_context();

View file

@ -7,6 +7,7 @@
#include <ultramodern/ultra64.h>
#include <unordered_set>
#include <variant>
namespace recompui {
struct UICallback {
@ -15,6 +16,8 @@ struct UICallback {
PTR(void) userdata;
};
using ElementValue = std::variant<uint32_t, float, double, std::monostate>;
class ContextId;
class Element : public Style, public Rml::EventListener {
friend ContextId create_context(const std::filesystem::path& path);
@ -52,6 +55,8 @@ 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&) {}
public:
// Used for backwards compatibility with legacy UI elements.
Element(Rml::Element *base);
@ -80,6 +85,12 @@ public:
float get_client_height();
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); }
};
void queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback);

View file

@ -1,3 +1,4 @@
#include "overloaded.h"
#include "ui_radio.h"
namespace recompui {
@ -14,14 +15,15 @@ namespace recompui {
set_line_height(20.0f);
set_font_weight(400);
set_font_style(FontStyle::Normal);
set_border_color(Color{ 242, 242, 242, 255 });
set_border_bottom_width(0.0f);
set_border_color(Color{ 242, 242, 242, 0 });
set_border_bottom_width(1.0f);
set_color(Color{ 255, 255, 255, 153 });
set_padding_bottom(8.0f);
set_text_transform(TextTransform::Uppercase);
set_height_auto();
hover_style.set_color(Color{ 255, 255, 255, 204 });
checked_style.set_color(Color{ 255, 255, 255, 255 });
checked_style.set_border_bottom_width(1.0f);
checked_style.set_border_color(Color{ 242, 242, 242, 255 });
add_style(&hover_style, { hover_state });
add_style(&checked_style, { checked_state });
@ -70,10 +72,19 @@ namespace recompui {
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) {
set_gap(24.0f);
set_flex_grow(0.0f);
set_align_items(AlignItems::FlexStart);
}
Radio::~Radio() {

View file

@ -26,6 +26,8 @@ namespace recompui {
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(); }
public:
Radio(Element *parent);
virtual ~Radio();

View file

@ -1,3 +1,4 @@
#include "overloaded.h"
#include "ui_slider.h"
#include <cmath>
@ -44,7 +45,8 @@ namespace recompui {
void Slider::update_circle_position() {
double ratio = std::clamp((value - min_value) / (max_value - min_value), 0.0, 1.0);
circle_element->set_left(slider_width_dp * ratio);
float slider_relative_left = slider_element->get_absolute_left() - get_absolute_left();
circle_element->set_left(ratio * 100.0, Unit::Percent);
}
void Slider::update_label_text() {
@ -60,13 +62,23 @@ namespace recompui {
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);
}
Slider::Slider(Element *parent, SliderType type) : Element(parent) {
this->type = type;
set_display(Display::Flex);
set_flex(1.0f, 1.0f, 100.0f, Unit::Percent);
set_flex_direction(FlexDirection::Row);
set_text_align(TextAlign::Left);
set_min_width(120.0f);
ContextId context = get_current_context();
@ -76,7 +88,7 @@ namespace recompui {
value_label->set_max_width(60.0f);
slider_element = context.create_element<Element>(this);
slider_element->set_width(slider_width_dp);
slider_element->set_flex(1.0f, 0.0f);
{
bar_element = context.create_element<Clickable>(slider_element, true);
@ -87,11 +99,11 @@ namespace recompui {
bar_element->add_pressed_callback([this](float x, float y){ bar_clicked(x, y); });
bar_element->add_dragged_callback([this](float x, float y, recompui::DragPhase phase){ bar_dragged(x, y, phase); });
circle_element = context.create_element<Clickable>(slider_element, true);
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(-8.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(Color{ 204, 204, 204, 255 });

View file

@ -22,7 +22,6 @@ namespace recompui {
double min_value = 0.0;
double max_value = 100.0;
double step_value = 0.0;
float slider_width_dp = 300.0;
std::vector<std::function<void(double)>> value_changed_callbacks;
void set_value_internal(double v, bool setup, bool trigger_callbacks);
@ -32,6 +31,8 @@ namespace recompui {
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(); }
public:
Slider(Element *parent, SliderType type);

View file

@ -21,9 +21,11 @@ namespace recompui {
}
}
TextInput::TextInput(Element *parent) : Element(parent, Events(EventType::Text), "input") {
TextInput::TextInput(Element *parent, bool text_visible) : Element(parent, Events(EventType::Text), "input") {
if (!text_visible) {
set_attribute("type", "password");
}
set_min_width(60.0f);
set_max_width(400.0f);
set_border_color(Color{ 242, 242, 242, 255 });
set_border_bottom_width(1.0f);
set_padding_bottom(6.0f);

View file

@ -11,7 +11,7 @@ namespace recompui {
protected:
virtual void process_event(const Event &e) override;
public:
TextInput(Element *parent);
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);

View file

@ -116,6 +116,16 @@ void recompui_destroy_element(uint8_t* rdram, recomp_context* ctx) {
}
}
void recompui_create_button(uint8_t* rdram, recomp_context* ctx) {
ContextId ui_context = get_context(rdram, ctx);
Element* parent = arg_element<1>(rdram, ctx, ui_context);
std::string text = _arg_string<2>(rdram, ctx);
uint32_t style = _arg<3, uint32_t>(rdram, ctx);
Button* ret = ui_context.create_element<Button>(parent, text, static_cast<ButtonStyle>(style));
return_resource(ctx, ret->get_resource_id());
}
void recompui_create_label(uint8_t* rdram, recomp_context* ctx) {
ContextId ui_context = get_context(rdram, ctx);
Element* parent = arg_element<1>(rdram, ctx, ui_context);
@ -143,13 +153,43 @@ void recompui_create_textinput(uint8_t* rdram, recomp_context* ctx) {
return_resource(ctx, ret->get_resource_id());
}
void recompui_create_button(uint8_t* rdram, recomp_context* ctx) {
void recompui_create_passwordinput(uint8_t* rdram, recomp_context* ctx) {
ContextId ui_context = get_context(rdram, ctx);
Element* parent = arg_element<1>(rdram, ctx, ui_context);
std::string text = _arg_string<2>(rdram, ctx);
uint32_t style = _arg<3, uint32_t>(rdram, ctx);
Button* ret = ui_context.create_element<Button>(parent, text, static_cast<ButtonStyle>(style));
Element* ret = ui_context.create_element<TextInput>(parent, false);
return_resource(ctx, ret->get_resource_id());
}
void recompui_create_labelradio(uint8_t* rdram, recomp_context* ctx) {
ContextId ui_context = get_context(rdram, ctx);
Element* parent = arg_element<1>(rdram, ctx, ui_context);
PTR(PTR(char)) options = _arg<2, PTR(PTR(char))>(rdram, ctx);
uint32_t num_options = _arg<3, uint32_t>(rdram, ctx);
Radio* ret = ui_context.create_element<Radio>(parent);
for (size_t i = 0; i < num_options; i++) {
ret->add_option(decode_string(rdram, MEM_W(sizeof(uint32_t) * i, options)));
}
return_resource(ctx, ret->get_resource_id());
}
void recompui_create_slider(uint8_t* rdram, recomp_context* ctx) {
ContextId ui_context = get_context(rdram, ctx);
Element* parent = arg_element<1>(rdram, ctx, ui_context);
uint32_t type = _arg<2, uint32_t>(rdram, ctx);
float min_value = arg_float3(rdram, ctx);
float max_value = arg_float4(rdram, ctx);
float step = arg_float5(rdram, ctx);
float initial_value = arg_float6(rdram, ctx);
Slider* ret = ui_context.create_element<Slider>(parent, static_cast<SliderType>(type));
ret->set_min_value(min_value);
ret->set_max_value(max_value);
ret->set_step_value(step);
ret->set_value(initial_value);
return_resource(ctx, ret->get_resource_id());
}
@ -699,7 +739,33 @@ void recompui_set_tab_index(uint8_t* rdram, recomp_context* ctx) {
resource->set_tab_index(static_cast<TabIndex>(tab_index));
}
// Text
// Values
void recompui_get_input_value_u32(uint8_t* rdram, recomp_context* ctx) {
Style* resource = arg_style<0>(rdram, ctx);
if (!resource->is_element()) {
recompui::message_box("Fatal error in mod - attempted to get value of non-element");
assert(false);
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
}
Element* element = static_cast<Element*>(resource);
_return<uint32_t>(ctx, element->get_input_value_u32());
}
void recompui_get_input_value_float(uint8_t* rdram, recomp_context* ctx) {
Style* resource = arg_style<0>(rdram, ctx);
if (!resource->is_element()) {
recompui::message_box("Fatal error in mod - attempted to get value of non-element");
assert(false);
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
}
Element* element = static_cast<Element*>(resource);
_return<float>(ctx, element->get_input_value_float());
}
void recompui_get_input_text(uint8_t* rdram, recomp_context* ctx) {
Style* resource = arg_style<0>(rdram, ctx);
@ -714,6 +780,34 @@ void recompui_get_input_text(uint8_t* rdram, recomp_context* ctx) {
return_string(rdram, ctx, ret);
}
void recompui_set_input_value_u32(uint8_t* rdram, recomp_context* ctx) {
Style* resource = arg_style<0>(rdram, ctx);
uint32_t value = _arg<1, uint32_t>(rdram, ctx);
if (!resource->is_element()) {
recompui::message_box("Fatal error in mod - attempted to set value of non-element");
assert(false);
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
}
Element* element = static_cast<Element*>(resource);
element->set_input_value_u32(value);
}
void recompui_set_input_value_float(uint8_t* rdram, recomp_context* ctx) {
Style* resource = arg_style<0>(rdram, ctx);
float value = _arg_float_a1(rdram, ctx);
if (!resource->is_element()) {
recompui::message_box("Fatal error in mod - attempted to set value of non-element");
assert(false);
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
}
Element* element = static_cast<Element*>(resource);
element->set_input_value_float(value);
}
void recompui_set_input_text(uint8_t* rdram, recomp_context* ctx) {
Style* resource = arg_style<0>(rdram, ctx);
@ -766,10 +860,13 @@ void recompui::register_ui_exports() {
REGISTER_FUNC(recompui_create_style);
REGISTER_FUNC(recompui_create_element);
REGISTER_FUNC(recompui_destroy_element);
REGISTER_FUNC(recompui_create_button);
REGISTER_FUNC(recompui_create_label);
// REGISTER_FUNC(recompui_create_span);
REGISTER_FUNC(recompui_create_textinput);
REGISTER_FUNC(recompui_create_button);
REGISTER_FUNC(recompui_create_passwordinput);
REGISTER_FUNC(recompui_create_labelradio);
REGISTER_FUNC(recompui_create_slider);
REGISTER_FUNC(recompui_set_visibility);
REGISTER_FUNC(recompui_set_position);
REGISTER_FUNC(recompui_set_left);
@ -841,7 +938,11 @@ void recompui::register_ui_exports() {
REGISTER_FUNC(recompui_set_column_gap);
REGISTER_FUNC(recompui_set_drag);
REGISTER_FUNC(recompui_set_tab_index);
REGISTER_FUNC(recompui_get_input_value_u32);
REGISTER_FUNC(recompui_get_input_value_float);
REGISTER_FUNC(recompui_get_input_text);
REGISTER_FUNC(recompui_set_input_value_u32);
REGISTER_FUNC(recompui_set_input_value_float);
REGISTER_FUNC(recompui_set_input_text);
REGISTER_FUNC(recompui_register_callback);
register_ui_image_exports();

View file

@ -1,5 +1,6 @@
#include "concurrentqueue.h"
#include "overloaded.h"
#include "recomp_ui.h"
#include "core/ui_context.h"
@ -24,11 +25,6 @@
#include "../patches/ui_funcs.h"
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
struct QueuedCallback {
recompui::ResourceId resource;
recompui::Event event;

View file

@ -67,6 +67,7 @@ ConfigOptionSlider::ConfigOptionSlider(Element *parent, double value, double min
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);
@ -84,6 +85,7 @@ ConfigOptionTextInput::ConfigOptionTextInput(Element *parent, std::string_view v
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); });
}
@ -147,7 +149,7 @@ ConfigSubMenu::ConfigSubMenu(Element *parent) : Element(parent) {
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_left(12.0f);
header_container->set_padding(12.0f);
header_container->set_gap(24.0f);
{

View file

@ -18,6 +18,56 @@ inline ContextId get_context(uint8_t* rdram, recomp_context* 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);
@ -82,6 +132,23 @@ inline void return_string(uint8_t* rdram, recomp_context* ctx, const std::string
_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

View file

@ -617,6 +617,11 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
case SDL_EventType::SDL_KEYDOWN:
non_mouse_interacted = true;
kb_interacted = true;
if (cur_event.key.keysym.scancode == SDL_Scancode::SDL_SCANCODE_F8) {
if (zelda64::get_debug_mode_enabled()) {
Rml::Debugger::SetVisible(!Rml::Debugger::IsVisible());
}
}
break;
case SDL_EventType::SDL_USEREVENT:
if (cur_event.user.code == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY) {