mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-04-05 05:56:48 +02:00
Implement update event for elements
This commit is contained in:
parent
dc07ca6b4d
commit
ab8f9a76e7
@ -48,9 +48,9 @@ namespace recompui {
|
||||
void show_context(ContextId context, std::string_view param);
|
||||
void hide_context(ContextId context);
|
||||
void hide_all_contexts();
|
||||
bool is_context_open(ContextId context);
|
||||
bool is_context_shown(ContextId context);
|
||||
bool is_context_taking_input();
|
||||
bool is_any_context_open();
|
||||
bool is_any_context_shown();
|
||||
|
||||
ContextId get_launcher_context_id();
|
||||
ContextId get_config_context_id();
|
||||
|
@ -157,7 +157,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
}
|
||||
|
||||
recompui::ContextId config_context_id = recompui::get_config_context_id();
|
||||
if (!recompui::is_context_open(config_context_id)) {
|
||||
if (!recompui::is_context_shown(config_context_id)) {
|
||||
recompui::show_context(config_context_id, "");
|
||||
}
|
||||
|
||||
@ -713,7 +713,7 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
|
||||
|
||||
bool recomp::game_input_disabled() {
|
||||
// Disable input if any menu that blocks input is open.
|
||||
return recompui::is_any_context_open();
|
||||
return recompui::is_context_taking_input();
|
||||
}
|
||||
|
||||
bool recomp::all_input_disabled() {
|
||||
|
@ -33,6 +33,7 @@ namespace recompui {
|
||||
Rml::ElementDocument* document;
|
||||
Element root_element;
|
||||
std::vector<Element*> loose_elements;
|
||||
std::unordered_set<ResourceId> to_update;
|
||||
Context(Rml::ElementDocument* document) : document(document), root_element(document) {}
|
||||
};
|
||||
} // namespace recompui
|
||||
@ -58,12 +59,15 @@ enum class ContextErrorType {
|
||||
GetContextWithoutOpen,
|
||||
AddResourceWithoutOpen,
|
||||
AddResourceToWrongContext,
|
||||
UpdateElementWithoutContext,
|
||||
UpdateElementInWrongContext,
|
||||
GetResourceWithoutOpen,
|
||||
GetResourceFailed,
|
||||
DestroyResourceWithoutOpen,
|
||||
DestroyResourceInWrongContext,
|
||||
DestroyResourceNotFound,
|
||||
GetDocumentInvalidContext,
|
||||
InternalError,
|
||||
};
|
||||
|
||||
enum class SlotTag : uint8_t {
|
||||
@ -101,6 +105,12 @@ void context_error(recompui::ContextId id, ContextErrorType type) {
|
||||
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::GetResourceWithoutOpen:
|
||||
error_message = "Attempted to get a UI resource with no open UI context";
|
||||
break;
|
||||
@ -119,6 +129,9 @@ void context_error(recompui::ContextId id, ContextErrorType type) {
|
||||
case ContextErrorType::GetDocumentInvalidContext:
|
||||
error_message = "Attempted to get the document 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;
|
||||
@ -317,6 +330,47 @@ void recompui::ContextId::close() {
|
||||
}
|
||||
}
|
||||
|
||||
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())->process_event(update_event);
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
@ -334,6 +388,8 @@ recompui::Style* recompui::ContextId::add_resource_impl(std::unique_ptr<Style>&&
|
||||
|
||||
if (is_element) {
|
||||
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));
|
||||
@ -357,6 +413,26 @@ void recompui::ContextId::add_loose_element(Element* element) {
|
||||
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);
|
||||
}
|
||||
|
||||
recompui::Style* recompui::ContextId::create_style() {
|
||||
return add_resource_impl(std::make_unique<Style>());
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ namespace recompui {
|
||||
}
|
||||
|
||||
void add_loose_element(Element* element);
|
||||
void queue_element_update(ResourceId element);
|
||||
|
||||
Style* create_style();
|
||||
|
||||
@ -42,6 +43,7 @@ namespace recompui {
|
||||
|
||||
void open();
|
||||
void close();
|
||||
void process_updates();
|
||||
|
||||
static constexpr ContextId null() { return ContextId{ .slot_id = uint32_t(-1) }; }
|
||||
|
||||
|
@ -78,6 +78,8 @@ namespace recompui {
|
||||
case EventType::Enable:
|
||||
set_style_enabled(disabled_state, !std::get<EventEnable>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
|
@ -308,4 +308,15 @@ float Element::get_client_height() {
|
||||
return base->GetClientHeight();
|
||||
}
|
||||
|
||||
void Element::queue_update() {
|
||||
ContextId cur_context = get_current_context();
|
||||
|
||||
// TODO disallow null contexts once the entire UI system has been migrated.
|
||||
if (cur_context == ContextId::null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cur_context.queue_element_update(resource_id);
|
||||
}
|
||||
|
||||
}
|
@ -6,9 +6,11 @@
|
||||
#include <unordered_set>
|
||||
|
||||
namespace recompui {
|
||||
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.
|
||||
private:
|
||||
Rml::Element *base = nullptr;
|
||||
Rml::ElementPtr base_owning = {};
|
||||
@ -60,6 +62,7 @@ public:
|
||||
float get_client_top();
|
||||
float get_client_width();
|
||||
float get_client_height();
|
||||
void queue_update();
|
||||
};
|
||||
|
||||
} // namespace recompui
|
@ -86,6 +86,9 @@ namespace recompui {
|
||||
floater->set_style_enabled(disabled_state, !enable_active);
|
||||
break;
|
||||
}
|
||||
case EventType::Update: {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
|
@ -29,6 +29,7 @@ namespace recompui {
|
||||
Enable,
|
||||
Drag,
|
||||
Text,
|
||||
Update,
|
||||
Count
|
||||
};
|
||||
|
||||
@ -76,7 +77,7 @@ namespace recompui {
|
||||
std::string text;
|
||||
};
|
||||
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText>;
|
||||
using EventVariant = std::variant<EventClick, EventFocus, EventHover, EventEnable, EventDrag, EventText, std::monostate>;
|
||||
|
||||
struct Event {
|
||||
EventType type;
|
||||
@ -124,6 +125,13 @@ namespace recompui {
|
||||
e.variant = EventText{ text };
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event update_event() {
|
||||
Event e;
|
||||
e.type = EventType::Update;
|
||||
e.variant = std::monostate{};
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
enum class Display {
|
||||
|
@ -134,7 +134,7 @@ void close_config_menu_impl() {
|
||||
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_open(sub_menu_context)) {
|
||||
if (recompui::is_context_shown(sub_menu_context)) {
|
||||
recompui::hide_context(sub_menu_context);
|
||||
}
|
||||
else {
|
||||
|
@ -15,6 +15,8 @@ void ConfigOptionElement::process_event(const Event &e) {
|
||||
case EventType::Hover:
|
||||
hover_callback(this, std::get<EventHover>(e.variant).active);
|
||||
break;
|
||||
case EventType::Update:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
|
@ -158,7 +158,7 @@ class UIState {
|
||||
bool mouse_is_active_changed = false;
|
||||
std::unique_ptr<recompui::MenuController> launcher_menu_controller{};
|
||||
std::unique_ptr<recompui::MenuController> config_menu_controller{};
|
||||
std::vector<ContextDetails> opened_contexts{};
|
||||
std::vector<ContextDetails> shown_contexts{};
|
||||
public:
|
||||
bool mouse_is_active_initialized = false;
|
||||
bool mouse_is_active = false;
|
||||
@ -337,13 +337,13 @@ public:
|
||||
}
|
||||
|
||||
void show_context(recompui::ContextId context) {
|
||||
if (std::find_if(opened_contexts.begin(), opened_contexts.end(), [context](auto& c){ return c.context == context; }) != opened_contexts.end()) {
|
||||
if (std::find_if(shown_contexts.begin(), shown_contexts.end(), [context](auto& c){ return c.context == context; }) != shown_contexts.end()) {
|
||||
recompui::message_box("Attemped to show the same context twice");
|
||||
assert(false);
|
||||
}
|
||||
bool takes_input = context.takes_input();
|
||||
Rml::ElementDocument* document = context.get_document();
|
||||
opened_contexts.push_back(ContextDetails{
|
||||
shown_contexts.push_back(ContextDetails{
|
||||
.context = context,
|
||||
.document = document,
|
||||
.takes_input = takes_input
|
||||
@ -361,45 +361,53 @@ public:
|
||||
}
|
||||
|
||||
void hide_context(recompui::ContextId context) {
|
||||
auto remove_it = std::remove_if(opened_contexts.begin(), opened_contexts.end(), [context](auto& c) { return c.context == context; });
|
||||
if (remove_it == opened_contexts.end()) {
|
||||
auto remove_it = std::remove_if(shown_contexts.begin(), shown_contexts.end(), [context](auto& c) { return c.context == context; });
|
||||
if (remove_it == shown_contexts.end()) {
|
||||
recompui::message_box("Attemped to hide a context that isn't shown");
|
||||
assert(false);
|
||||
}
|
||||
opened_contexts.erase(remove_it, opened_contexts.end());
|
||||
shown_contexts.erase(remove_it, shown_contexts.end());
|
||||
|
||||
context.get_document()->Hide();
|
||||
}
|
||||
|
||||
void hide_all_contexts() {
|
||||
for (auto& context : opened_contexts) {
|
||||
for (auto& context : shown_contexts) {
|
||||
context.document->Hide();
|
||||
}
|
||||
|
||||
opened_contexts.clear();
|
||||
shown_contexts.clear();
|
||||
}
|
||||
|
||||
bool is_context_open(recompui::ContextId context) {
|
||||
return std::find_if(opened_contexts.begin(), opened_contexts.end(), [context](auto& c){ return c.context == context; }) != opened_contexts.end();
|
||||
bool is_context_shown(recompui::ContextId context) {
|
||||
return std::find_if(shown_contexts.begin(), shown_contexts.end(), [context](auto& c){ return c.context == context; }) != shown_contexts.end();
|
||||
}
|
||||
|
||||
bool is_context_taking_input() {
|
||||
return std::find_if(opened_contexts.begin(), opened_contexts.end(), [](auto& c){ return c.takes_input; }) != opened_contexts.end();
|
||||
return std::find_if(shown_contexts.begin(), shown_contexts.end(), [](auto& c){ return c.takes_input; }) != shown_contexts.end();
|
||||
}
|
||||
|
||||
bool is_any_context_open() {
|
||||
return !opened_contexts.empty();
|
||||
bool is_any_context_shown() {
|
||||
return !shown_contexts.empty();
|
||||
}
|
||||
|
||||
Rml::ElementDocument* top_input_document() {
|
||||
// Iterate backwards and stop at the first context that takes input.
|
||||
for (auto it = opened_contexts.rbegin(); it != opened_contexts.rend(); it++) {
|
||||
for (auto it = shown_contexts.rbegin(); it != shown_contexts.rend(); it++) {
|
||||
if (it->takes_input) {
|
||||
return it->document;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void update_contexts() {
|
||||
for (auto& context_details : shown_contexts) {
|
||||
context_details.context.open();
|
||||
context_details.context.process_updates();
|
||||
context_details.context.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<UIState> ui_state;
|
||||
@ -531,7 +539,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
|
||||
// Return to the launcher if no menu is open and the game isn't started.
|
||||
if (!recompui::is_any_context_open() && !ultramodern::is_game_started()) {
|
||||
if (!recompui::is_any_context_shown() && !ultramodern::is_game_started()) {
|
||||
recompui::show_context(recompui::get_launcher_context_id(), "");
|
||||
}
|
||||
|
||||
@ -545,7 +553,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
bool cont_interacted = false;
|
||||
bool kb_interacted = false;
|
||||
|
||||
bool config_was_open = recompui::is_context_open(recompui::get_config_context_id()) || recompui::is_context_open(recompui::get_config_sub_menu_context_id());
|
||||
bool config_was_open = recompui::is_context_shown(recompui::get_config_context_id()) || recompui::is_context_shown(recompui::get_config_sub_menu_context_id());
|
||||
|
||||
while (recompui::try_deque_event(cur_event)) {
|
||||
bool context_taking_input = recompui::is_context_taking_input();
|
||||
@ -669,7 +677,9 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
ui_state->update_primary_input(mouse_moved, non_mouse_interacted);
|
||||
ui_state->update_focus(mouse_moved, non_mouse_interacted);
|
||||
|
||||
if (recompui::is_any_context_open()) {
|
||||
if (recompui::is_any_context_shown()) {
|
||||
ui_state->update_contexts();
|
||||
|
||||
int width = swap_chain_framebuffer->getWidth();
|
||||
int height = swap_chain_framebuffer->getHeight();
|
||||
|
||||
@ -733,14 +743,14 @@ void recompui::hide_all_contexts() {
|
||||
}
|
||||
}
|
||||
|
||||
bool recompui::is_context_open(ContextId context) {
|
||||
bool recompui::is_context_shown(ContextId context) {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
if (!ui_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ui_state->is_context_open(context);
|
||||
return ui_state->is_context_shown(context);
|
||||
}
|
||||
|
||||
bool recompui::is_context_taking_input() {
|
||||
@ -753,14 +763,14 @@ bool recompui::is_context_taking_input() {
|
||||
return ui_state->is_context_taking_input();
|
||||
}
|
||||
|
||||
bool recompui::is_any_context_open() {
|
||||
bool recompui::is_any_context_shown() {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
if (!ui_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ui_state->is_any_context_open();
|
||||
return ui_state->is_any_context_shown();
|
||||
}
|
||||
|
||||
Rml::ElementDocument* recompui::load_document(const std::filesystem::path& path) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user