mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-03-04 10:45:23 +01:00
Implemented mod UI callbacks
This commit is contained in:
parent
584903ca18
commit
8f85fb74db
@ -166,6 +166,7 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_details_panel.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/util/hsv.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp
|
||||
|
14
patches/gamestate_patches.c
Normal file
14
patches/gamestate_patches.c
Normal file
@ -0,0 +1,14 @@
|
||||
#include "patches.h"
|
||||
#include "ui_funcs.h"
|
||||
|
||||
// @recomp Patched to run UI callbacks.
|
||||
RECOMP_PATCH void Graph_UpdateGame(GameState* gameState) {
|
||||
recomp_run_ui_callbacks();
|
||||
|
||||
GameState_GetInput(gameState);
|
||||
GameState_IncrementFrameCount(gameState);
|
||||
if (SREG(20) < 3) {
|
||||
Audio_Update();
|
||||
}
|
||||
}
|
||||
|
52
patches/recompui_event_structs.h
Normal file
52
patches/recompui_event_structs.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef __UI_FUNCS_H__
|
||||
#define __UI_FUNCS_H__
|
||||
|
||||
// These two enums must be kept in sync with src/ui/elements/ui_types.h!
|
||||
typedef enum {
|
||||
UI_EVENT_NONE,
|
||||
UI_EVENT_CLICK,
|
||||
UI_EVENT_FOCUS,
|
||||
UI_EVENT_HOVER,
|
||||
UI_EVENT_ENABLE,
|
||||
UI_EVENT_DRAG,
|
||||
UI_EVENT_RESERVED1, // Would be UI_EVENT_TEXT but text events aren't usable in mods currently
|
||||
UI_EVENT_UPDATE,
|
||||
UI_EVENT_COUNT
|
||||
} RecompuiEventType;
|
||||
|
||||
typedef enum {
|
||||
UI_DRAG_NONE,
|
||||
UI_DRAG_START,
|
||||
UI_DRAG_MOVE,
|
||||
UI_DRAG_END
|
||||
} RecompuiDragPhase;
|
||||
|
||||
typedef struct {
|
||||
RecompuiEventType type;
|
||||
union {
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
} click;
|
||||
|
||||
struct {
|
||||
bool active;
|
||||
} focus;
|
||||
|
||||
struct {
|
||||
bool active;
|
||||
} hover;
|
||||
|
||||
struct {
|
||||
bool active;
|
||||
} enable;
|
||||
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
RecompuiDragPhase phase;
|
||||
} drag;
|
||||
} data;
|
||||
} RecompuiEventData;
|
||||
|
||||
#endif
|
@ -45,3 +45,4 @@ recomp_high_precision_fb_enabled = 0x8F0000A8;
|
||||
recomp_get_resolution_scale = 0x8F0000AC;
|
||||
recomp_get_analog_inverted_axes = 0x8F0000B0;
|
||||
recomp_get_window_resolution = 0x8F0000B4;
|
||||
recomp_run_ui_callbacks = 0x8F0000B8;
|
||||
|
9
patches/ui_funcs.h
Normal file
9
patches/ui_funcs.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef __UI_FUNCS_INTERNAL_H__
|
||||
#define __UI_FUNCS_INTERNAL_H__
|
||||
|
||||
#include "patch_helpers.h"
|
||||
#include "recompui_event_structs.h"
|
||||
|
||||
DECLARE_FUNC(void, recomp_run_ui_callbacks);
|
||||
|
||||
#endif
|
@ -367,7 +367,7 @@ void recompui::ContextId::process_updates() {
|
||||
continue;
|
||||
}
|
||||
|
||||
static_cast<Element*>(cur_resource->get())->process_event(update_event);
|
||||
static_cast<Element*>(cur_resource->get())->handle_event(update_event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ void Element::propagate_disabled(bool disabled) {
|
||||
base->SetAttribute("disabled", attribute_state);
|
||||
|
||||
if (events_enabled & Events(EventType::Enable)) {
|
||||
process_event(Event::enable_event(!attribute_state));
|
||||
handle_event(Event::enable_event(!attribute_state));
|
||||
}
|
||||
|
||||
for (auto &child : children) {
|
||||
@ -119,6 +119,14 @@ void Element::propagate_disabled(bool disabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void Element::handle_event(const Event& event) {
|
||||
for (const auto& callback : callbacks) {
|
||||
recompui::queue_ui_callback(resource_id, event, callback);
|
||||
}
|
||||
|
||||
process_event(event);
|
||||
}
|
||||
|
||||
void Element::ProcessEvent(Rml::Event &event) {
|
||||
ContextId context = ContextId::null();
|
||||
Rml::ElementDocument* doc = event.GetTargetElement()->GetOwnerDocument();
|
||||
@ -134,10 +142,10 @@ void Element::ProcessEvent(Rml::Event &event) {
|
||||
// Events that are processed during any phase.
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Mousedown:
|
||||
process_event(Event::click_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)));
|
||||
handle_event(Event::click_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f)));
|
||||
break;
|
||||
case Rml::EventId::Drag:
|
||||
process_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Move));
|
||||
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Move));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -147,28 +155,28 @@ void Element::ProcessEvent(Rml::Event &event) {
|
||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Mouseover:
|
||||
process_event(Event::hover_event(true));
|
||||
handle_event(Event::hover_event(true));
|
||||
break;
|
||||
case Rml::EventId::Mouseout:
|
||||
process_event(Event::hover_event(false));
|
||||
handle_event(Event::hover_event(false));
|
||||
break;
|
||||
case Rml::EventId::Focus:
|
||||
process_event(Event::focus_event(true));
|
||||
handle_event(Event::focus_event(true));
|
||||
break;
|
||||
case Rml::EventId::Blur:
|
||||
process_event(Event::focus_event(false));
|
||||
handle_event(Event::focus_event(false));
|
||||
break;
|
||||
case Rml::EventId::Dragstart:
|
||||
process_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Start));
|
||||
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::Start));
|
||||
break;
|
||||
case Rml::EventId::Dragend:
|
||||
process_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::End));
|
||||
handle_event(Event::drag_event(event.GetParameter("mouse_x", 0.0f), event.GetParameter("mouse_y", 0.0f), DragPhase::End));
|
||||
break;
|
||||
case Rml::EventId::Change: {
|
||||
if (events_enabled & Events(EventType::Text)) {
|
||||
Rml::Variant *value_variant = base->GetAttribute("value");
|
||||
if (value_variant != nullptr) {
|
||||
process_event(Event::text_event(value_variant->Get<std::string>()));
|
||||
handle_event(Event::text_event(value_variant->Get<std::string>()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,4 +317,8 @@ void Element::queue_update() {
|
||||
cur_context.queue_element_update(resource_id);
|
||||
}
|
||||
|
||||
void Element::register_callback(PTR(void) callback, PTR(void) userdata) {
|
||||
callbacks.emplace_back(UICallback{.callback = callback, .userdata = userdata});
|
||||
}
|
||||
|
||||
}
|
@ -3,14 +3,22 @@
|
||||
#include "ui_style.h"
|
||||
#include "../core/ui_context.h"
|
||||
|
||||
#include "recomp.h"
|
||||
#include <ultramodern/ultra64.h>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace recompui {
|
||||
struct UICallback {
|
||||
PTR(void) callback;
|
||||
PTR(void) userdata;
|
||||
};
|
||||
|
||||
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 process_event method directly.
|
||||
friend class ContextId; // To allow ContextId to call the handle_event method directly.
|
||||
private:
|
||||
Rml::Element *base = nullptr;
|
||||
Rml::ElementPtr base_owning = {};
|
||||
@ -19,6 +27,7 @@ private:
|
||||
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;
|
||||
std::vector<Element *> children;
|
||||
bool shim = false;
|
||||
bool enabled = true;
|
||||
@ -30,6 +39,7 @@ private:
|
||||
void apply_style(Style *style);
|
||||
void apply_styles();
|
||||
void propagate_disabled(bool disabled);
|
||||
void handle_event(const Event &e);
|
||||
|
||||
// Style overrides.
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property) override;
|
||||
@ -63,6 +73,9 @@ public:
|
||||
float get_client_width();
|
||||
float get_client_height();
|
||||
void queue_update();
|
||||
void register_callback(PTR(void) callback, PTR(void) userdata);
|
||||
};
|
||||
|
||||
void queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback);
|
||||
|
||||
} // namespace recompui
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
|
||||
using namespace recompui;
|
||||
|
||||
@ -696,6 +697,22 @@ void recompui_set_tab_index(uint8_t* rdram, recomp_context* ctx) {
|
||||
resource->set_tab_index(static_cast<TabIndex>(tab_index));
|
||||
}
|
||||
|
||||
void recompui_register_callback(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 register callback on non-element");
|
||||
assert(false);
|
||||
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||
}
|
||||
|
||||
Element* element = static_cast<Element*>(resource);
|
||||
PTR(void) callback = _arg<1, PTR(void)>(rdram, ctx);
|
||||
PTR(void) userdata = _arg<2, PTR(void)>(rdram, ctx);
|
||||
|
||||
element->register_callback(callback, userdata);
|
||||
}
|
||||
|
||||
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||
|
||||
void recompui::register_ui_exports() {
|
||||
@ -777,4 +794,5 @@ 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_register_callback);
|
||||
}
|
||||
|
118
src/ui/ui_api_events.cpp
Normal file
118
src/ui/ui_api_events.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include "concurrentqueue.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"
|
||||
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
ctx->r29 += sizeof(RecompuiEventData);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user