mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-03-04 18:55:21 +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_details_panel.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.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.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.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_resolution_scale = 0x8F0000AC;
|
||||||
recomp_get_analog_inverted_axes = 0x8F0000B0;
|
recomp_get_analog_inverted_axes = 0x8F0000B0;
|
||||||
recomp_get_window_resolution = 0x8F0000B4;
|
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;
|
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);
|
base->SetAttribute("disabled", attribute_state);
|
||||||
|
|
||||||
if (events_enabled & Events(EventType::Enable)) {
|
if (events_enabled & Events(EventType::Enable)) {
|
||||||
process_event(Event::enable_event(!attribute_state));
|
handle_event(Event::enable_event(!attribute_state));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &child : children) {
|
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) {
|
void Element::ProcessEvent(Rml::Event &event) {
|
||||||
ContextId context = ContextId::null();
|
ContextId context = ContextId::null();
|
||||||
Rml::ElementDocument* doc = event.GetTargetElement()->GetOwnerDocument();
|
Rml::ElementDocument* doc = event.GetTargetElement()->GetOwnerDocument();
|
||||||
@ -134,10 +142,10 @@ void Element::ProcessEvent(Rml::Event &event) {
|
|||||||
// Events that are processed during any phase.
|
// Events that are processed during any phase.
|
||||||
switch (event.GetId()) {
|
switch (event.GetId()) {
|
||||||
case Rml::EventId::Mousedown:
|
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;
|
break;
|
||||||
case Rml::EventId::Drag:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -147,28 +155,28 @@ void Element::ProcessEvent(Rml::Event &event) {
|
|||||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||||
switch (event.GetId()) {
|
switch (event.GetId()) {
|
||||||
case Rml::EventId::Mouseover:
|
case Rml::EventId::Mouseover:
|
||||||
process_event(Event::hover_event(true));
|
handle_event(Event::hover_event(true));
|
||||||
break;
|
break;
|
||||||
case Rml::EventId::Mouseout:
|
case Rml::EventId::Mouseout:
|
||||||
process_event(Event::hover_event(false));
|
handle_event(Event::hover_event(false));
|
||||||
break;
|
break;
|
||||||
case Rml::EventId::Focus:
|
case Rml::EventId::Focus:
|
||||||
process_event(Event::focus_event(true));
|
handle_event(Event::focus_event(true));
|
||||||
break;
|
break;
|
||||||
case Rml::EventId::Blur:
|
case Rml::EventId::Blur:
|
||||||
process_event(Event::focus_event(false));
|
handle_event(Event::focus_event(false));
|
||||||
break;
|
break;
|
||||||
case Rml::EventId::Dragstart:
|
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;
|
break;
|
||||||
case Rml::EventId::Dragend:
|
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;
|
break;
|
||||||
case Rml::EventId::Change: {
|
case Rml::EventId::Change: {
|
||||||
if (events_enabled & Events(EventType::Text)) {
|
if (events_enabled & Events(EventType::Text)) {
|
||||||
Rml::Variant *value_variant = base->GetAttribute("value");
|
Rml::Variant *value_variant = base->GetAttribute("value");
|
||||||
if (value_variant != nullptr) {
|
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);
|
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 "ui_style.h"
|
||||||
#include "../core/ui_context.h"
|
#include "../core/ui_context.h"
|
||||||
|
|
||||||
|
#include "recomp.h"
|
||||||
|
#include <ultramodern/ultra64.h>
|
||||||
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace recompui {
|
namespace recompui {
|
||||||
|
struct UICallback {
|
||||||
|
PTR(void) callback;
|
||||||
|
PTR(void) userdata;
|
||||||
|
};
|
||||||
|
|
||||||
class ContextId;
|
class ContextId;
|
||||||
class Element : public Style, public Rml::EventListener {
|
class Element : public Style, public Rml::EventListener {
|
||||||
friend ContextId create_context(const std::filesystem::path& path);
|
friend ContextId create_context(const std::filesystem::path& path);
|
||||||
friend ContextId create_context();
|
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:
|
private:
|
||||||
Rml::Element *base = nullptr;
|
Rml::Element *base = nullptr;
|
||||||
Rml::ElementPtr base_owning = {};
|
Rml::ElementPtr base_owning = {};
|
||||||
@ -19,6 +27,7 @@ private:
|
|||||||
std::vector<uint32_t> styles_counter;
|
std::vector<uint32_t> styles_counter;
|
||||||
std::unordered_set<std::string_view> style_active_set;
|
std::unordered_set<std::string_view> style_active_set;
|
||||||
std::unordered_multimap<std::string_view, uint32_t> style_name_index_map;
|
std::unordered_multimap<std::string_view, uint32_t> style_name_index_map;
|
||||||
|
std::vector<UICallback> callbacks;
|
||||||
std::vector<Element *> children;
|
std::vector<Element *> children;
|
||||||
bool shim = false;
|
bool shim = false;
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
@ -30,6 +39,7 @@ private:
|
|||||||
void apply_style(Style *style);
|
void apply_style(Style *style);
|
||||||
void apply_styles();
|
void apply_styles();
|
||||||
void propagate_disabled(bool disabled);
|
void propagate_disabled(bool disabled);
|
||||||
|
void handle_event(const Event &e);
|
||||||
|
|
||||||
// Style overrides.
|
// Style overrides.
|
||||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property) override;
|
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_width();
|
||||||
float get_client_height();
|
float get_client_height();
|
||||||
void queue_update();
|
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
|
} // namespace recompui
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "librecomp/overlays.hpp"
|
#include "librecomp/overlays.hpp"
|
||||||
#include "librecomp/helpers.hpp"
|
#include "librecomp/helpers.hpp"
|
||||||
|
#include "ultramodern/error_handling.hpp"
|
||||||
|
|
||||||
using namespace recompui;
|
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));
|
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)
|
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||||
|
|
||||||
void recompui::register_ui_exports() {
|
void recompui::register_ui_exports() {
|
||||||
@ -777,4 +794,5 @@ void recompui::register_ui_exports() {
|
|||||||
REGISTER_FUNC(recompui_set_column_gap);
|
REGISTER_FUNC(recompui_set_column_gap);
|
||||||
REGISTER_FUNC(recompui_set_drag);
|
REGISTER_FUNC(recompui_set_drag);
|
||||||
REGISTER_FUNC(recompui_set_tab_index);
|
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