mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-03-12 14:56:37 +01:00
Implement context and resource storage slotmaps
This commit is contained in:
parent
4e9bbbd290
commit
5c11adda89
@ -1,3 +1,466 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "slot_map.h"
|
||||
|
||||
#include "ui_context.hpp"
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
#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;
|
||||
std::vector<Element*> loose_elements;
|
||||
Context(Rml::ElementDocument* document) : document(document), root_element(document) {}
|
||||
};
|
||||
} // namespace recompui
|
||||
|
||||
using context_slotmap = dod::slot_map32<recompui::Context>;
|
||||
|
||||
static struct {
|
||||
std::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;
|
||||
} 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,
|
||||
GetResourceWithoutOpen,
|
||||
GetResourceFailed,
|
||||
DestroyResourceWithoutOpen,
|
||||
DestroyResourceInWrongContext,
|
||||
DestroyResourceNotFound,
|
||||
GetDocumentWithoutOpen,
|
||||
GetDocumentInWrongContext,
|
||||
};
|
||||
|
||||
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::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::GetDocumentWithoutOpen:
|
||||
error_message = "Attempted to get the current UI context's document with no open UI context";
|
||||
break;
|
||||
case ContextErrorType::GetDocumentInWrongContext:
|
||||
error_message = "Attempted to get the document of a UI context that's not open";
|
||||
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.
|
||||
ultramodern::error_handling::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;
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::create_context(Rml::Context* rml_context, const std::filesystem::path& path) {
|
||||
ContextId new_context = create_context_impl(nullptr);
|
||||
|
||||
new_context.open();
|
||||
Rml::ElementDocument* doc = rml_context->LoadDocument(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);
|
||||
}
|
||||
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
|
||||
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::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) {
|
||||
key.set_tag(static_cast<uint8_t>(SlotTag::Element));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
recompui::Style* recompui::ContextId::create_style() {
|
||||
return add_resource_impl(std::make_unique<Style>());
|
||||
}
|
||||
|
||||
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() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::GetDocumentWithoutOpen);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInWrongContext);
|
||||
}
|
||||
|
||||
return opened_context->document;
|
||||
}
|
||||
|
||||
recompui::Element* recompui::ContextId::get_root_element() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::GetDocumentWithoutOpen);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInWrongContext);
|
||||
}
|
||||
|
||||
return &opened_context->root_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;
|
||||
}
|
||||
|
54
src/ui/core/ui_context.h
Normal file
54
src/ui/core/ui_context.h
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <filesystem>
|
||||
|
||||
#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);
|
||||
|
||||
Style* create_style();
|
||||
|
||||
void destroy_resource(Style* resource);
|
||||
void destroy_resource(ResourceId resource);
|
||||
void clear_children();
|
||||
|
||||
Rml::ElementDocument* get_document();
|
||||
Element* get_root_element();
|
||||
|
||||
void open();
|
||||
void close();
|
||||
|
||||
static constexpr ContextId null() { return ContextId{ .slot_id = uint32_t(-1) }; }
|
||||
};
|
||||
|
||||
ContextId create_context(Rml::Context*, const std::filesystem::path& path);
|
||||
ContextId create_context(Rml::ElementDocument* document);
|
||||
void destroy_context(ContextId id);
|
||||
ContextId get_current_context();
|
||||
ContextId get_context_from_document(Rml::ElementDocument* document);
|
||||
void destroy_all_contexts();
|
||||
} // namespace recompui
|
@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../elements/ui_element.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
} // namespace recompui
|
||||
|
22
src/ui/core/ui_resource.h
Normal file
22
src/ui/core/ui_resource.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace recompui {
|
||||
class Style;
|
||||
class Element;
|
||||
struct ResourceId {
|
||||
uint32_t slot_id;
|
||||
|
||||
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
|
@ -76,7 +76,7 @@ void ElementConfigOption::AddOptionTypeElement() {
|
||||
|
||||
switch (el_option_type) {
|
||||
default:
|
||||
printf("No option type element exists for type '%d'\n", el_option_type);
|
||||
printf("No option type element exists for type '%u'\n", static_cast<uint32_t>(el_option_type));
|
||||
return;
|
||||
case ConfigOptionType::Button: {
|
||||
add_option_el<ElementOptionTypeButton>(doc, wrapper, "recomp-option-type-button", config_key);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "ui_element.h"
|
||||
#include "../core/ui_context.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@ -8,36 +9,28 @@ Element::Element(Rml::Element *base) {
|
||||
assert(base != nullptr);
|
||||
|
||||
this->base = base;
|
||||
this->owner = false;
|
||||
this->base_owning = {};
|
||||
this->shim = true;
|
||||
}
|
||||
|
||||
Element::Element(Element *parent, uint32_t events_enabled, Rml::String base_class) {
|
||||
owner = true;
|
||||
|
||||
Rml::ElementPtr element = parent->base->GetOwnerDocument()->CreateElement(base_class);
|
||||
Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_class) {
|
||||
ContextId context = get_current_context();
|
||||
base_owning = context.get_document()->CreateElement(base_class);
|
||||
if (parent != nullptr) {
|
||||
base = parent->base->AppendChild(std::move(element));
|
||||
|
||||
if (parent->owner) {
|
||||
parent->add_child(this);
|
||||
}
|
||||
base = parent->base->AppendChild(std::move(base_owning));
|
||||
parent->add_child(this);
|
||||
}
|
||||
else {
|
||||
base = element.release();
|
||||
orphaned = true;
|
||||
base = base_owning.get();
|
||||
}
|
||||
|
||||
register_event_listeners(events_enabled);
|
||||
}
|
||||
|
||||
Element::~Element() {
|
||||
children.clear();
|
||||
|
||||
if (owner) {
|
||||
if (orphaned) {
|
||||
delete base;
|
||||
}
|
||||
else {
|
||||
if (!shim) {
|
||||
clear_children();
|
||||
if (!base_owning) {
|
||||
base->GetParentNode()->RemoveChild(base);
|
||||
}
|
||||
}
|
||||
@ -47,6 +40,11 @@ void Element::add_child(Element *child) {
|
||||
assert(child != nullptr);
|
||||
|
||||
children.emplace_back(child);
|
||||
|
||||
if (shim) {
|
||||
ContextId context = get_current_context();
|
||||
context.add_loose_element(child);
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation animation) {
|
||||
@ -121,6 +119,17 @@ void Element::propagate_disabled(bool disabled) {
|
||||
}
|
||||
|
||||
void Element::ProcessEvent(Rml::Event &event) {
|
||||
ContextId context = ContextId::null();
|
||||
Rml::ElementDocument* doc = event.GetTargetElement()->GetOwnerDocument();
|
||||
if (doc != nullptr) {
|
||||
context = get_context_from_document(doc);
|
||||
}
|
||||
|
||||
// TODO disallow null contexts once the entire UI system has been migrated.
|
||||
if (context != ContextId::null()) {
|
||||
context.open();
|
||||
}
|
||||
|
||||
// Events that are processed during any phase.
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Mousedown:
|
||||
@ -149,6 +158,10 @@ void Element::ProcessEvent(Rml::Event &event) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (context != ContextId::null()) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
void Element::process_event(const Event &) {
|
||||
@ -156,6 +169,14 @@ void Element::process_event(const Event &) {
|
||||
}
|
||||
|
||||
void Element::clear_children() {
|
||||
ContextId context = get_current_context();
|
||||
|
||||
// Remove the children from the context.
|
||||
for (Element* child : children) {
|
||||
context.destroy_resource(child);
|
||||
}
|
||||
|
||||
// Clear the child list.
|
||||
children.clear();
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui_style.h"
|
||||
#include "../core/ui_context.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class Element : public Style, public Rml::EventListener {
|
||||
friend class Element;
|
||||
friend ContextId create_context(Rml::Context* rml_context, const std::filesystem::path& path);
|
||||
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<std::unique_ptr<Element>> children;
|
||||
bool owner = false;
|
||||
bool orphaned = false;
|
||||
std::vector<Element *> children;
|
||||
bool shim = false;
|
||||
bool enabled = true;
|
||||
bool disabled_attribute = false;
|
||||
bool disabled_from_parent = false;
|
||||
@ -32,7 +32,7 @@ private:
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation animation) override;
|
||||
|
||||
// Rml::EventListener overrides.
|
||||
virtual void ProcessEvent(Rml::Event &event) override;
|
||||
void ProcessEvent(Rml::Event &event) override final;
|
||||
protected:
|
||||
virtual void process_event(const Event &e);
|
||||
public:
|
||||
@ -40,7 +40,7 @@ public:
|
||||
Element(Rml::Element *base);
|
||||
|
||||
// Used to actually construct elements.
|
||||
Element(Element *parent, uint32_t events_enabled = 0, Rml::String base_class = "div");
|
||||
Element(Element* parent, uint32_t events_enabled = 0, Rml::String base_class = "div");
|
||||
virtual ~Element();
|
||||
void clear_children();
|
||||
void add_style(Style *style, const std::string_view style_name);
|
||||
@ -49,6 +49,8 @@ public:
|
||||
bool is_enabled() const;
|
||||
void set_text(const std::string &text);
|
||||
void set_style_enabled(const std::string_view &style_name, bool enabled);
|
||||
|
||||
bool is_element() override { return true; }
|
||||
};
|
||||
|
||||
} // namespace recompui
|
@ -2,17 +2,19 @@
|
||||
|
||||
#include "RmlUi/Core.h"
|
||||
|
||||
#include "../core/ui_resource.h"
|
||||
#include "ui_types.h"
|
||||
|
||||
namespace recompui {
|
||||
|
||||
class ContextId;
|
||||
class Style {
|
||||
friend class Element;
|
||||
friend 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;
|
||||
protected:
|
||||
virtual void set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation);
|
||||
ResourceId resource_id = ResourceId::null();
|
||||
public:
|
||||
Style();
|
||||
virtual ~Style();
|
||||
@ -80,6 +82,9 @@ namespace recompui {
|
||||
void set_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
|
||||
void set_row_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
|
||||
void set_column_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
|
||||
|
||||
virtual bool is_element() { return false; }
|
||||
ResourceId get_resource_id() { return resource_id; }
|
||||
};
|
||||
|
||||
} // namespace recompui
|
@ -30,7 +30,9 @@ namespace recompui {
|
||||
add_style(&disabled_style, disabled_state);
|
||||
add_style(&checked_disabled_style, { checked_state, disabled_state });
|
||||
|
||||
floater = new Element(this);
|
||||
ContextId context = get_current_context();
|
||||
|
||||
floater = context.create_element<Element>(this);
|
||||
floater->set_position(Position::Relative);
|
||||
floater->set_top(2.0f);
|
||||
floater->set_width(80.0f);
|
||||
|
@ -9,6 +9,8 @@
|
||||
#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;
|
||||
@ -511,6 +513,8 @@ void recompui::update_rml_display_refresh_rate() {
|
||||
DebugContext debug_context;
|
||||
|
||||
class ConfigMenu : public recompui::MenuController {
|
||||
private:
|
||||
recompui::ContextId config_context;
|
||||
public:
|
||||
ConfigMenu() {
|
||||
|
||||
@ -519,7 +523,11 @@ public:
|
||||
|
||||
}
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/config_menu.rml");
|
||||
config_context = recompui::create_context(context, "assets/config_menu.rml");
|
||||
config_context.open();
|
||||
Rml::ElementDocument* ret = config_context.get_document();
|
||||
config_context.close();
|
||||
return ret;
|
||||
}
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "apply_options",
|
||||
|
@ -12,52 +12,54 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
||||
set_border_bottom_right_radius(16.0f);
|
||||
set_background_color(Color{ 190, 184, 219, 25 });
|
||||
|
||||
header_container = new Container(FlexDirection::Row, JustifyContent::FlexStart, this);
|
||||
ContextId context = get_current_context();
|
||||
|
||||
header_container = context.create_element<Container>(FlexDirection::Row, JustifyContent::FlexStart, this);
|
||||
header_container->set_flex(0.0f, 0.0f);
|
||||
header_container->set_padding(16.0f);
|
||||
header_container->set_gap(16.0f);
|
||||
header_container->set_background_color(Color{ 0, 0, 0, 89 });
|
||||
{
|
||||
thumbnail_container = new Container(FlexDirection::Column, JustifyContent::SpaceEvenly, header_container);
|
||||
thumbnail_container = context.create_element<Container>(FlexDirection::Column, JustifyContent::SpaceEvenly, header_container);
|
||||
thumbnail_container->set_flex(0.0f, 0.0f);
|
||||
{
|
||||
thumbnail_image = new Image(thumbnail_container);
|
||||
thumbnail_image = context.create_element<Image>(thumbnail_container);
|
||||
thumbnail_image->set_width(100.0f);
|
||||
thumbnail_image->set_height(100.0f);
|
||||
thumbnail_image->set_background_color(Color{ 190, 184, 219, 25 });
|
||||
}
|
||||
|
||||
header_details_container = new Container(FlexDirection::Column, JustifyContent::SpaceEvenly, header_container);
|
||||
header_details_container = context.create_element<Container>(FlexDirection::Column, JustifyContent::SpaceEvenly, header_container);
|
||||
header_details_container->set_flex(1.0f, 1.0f);
|
||||
header_details_container->set_flex_basis(100.0f, Unit::Percent);
|
||||
header_details_container->set_text_align(TextAlign::Left);
|
||||
{
|
||||
title_label = new Label(LabelStyle::Large, header_details_container);
|
||||
version_label = new Label(LabelStyle::Normal, header_details_container);
|
||||
title_label = context.create_element<Label>(LabelStyle::Large, header_details_container);
|
||||
version_label = context.create_element<Label>(LabelStyle::Normal, header_details_container);
|
||||
}
|
||||
}
|
||||
|
||||
body_container = new recompui::Container(FlexDirection::Column, JustifyContent::FlexStart, this);
|
||||
body_container = context.create_element<Container>(FlexDirection::Column, JustifyContent::FlexStart, this);
|
||||
body_container->set_flex(0.0f, 0.0f);
|
||||
body_container->set_text_align(TextAlign::Left);
|
||||
body_container->set_padding(16.0f);
|
||||
body_container->set_gap(16.0f);
|
||||
{
|
||||
description_label = new Label(LabelStyle::Normal, body_container);
|
||||
authors_label = new Label(LabelStyle::Normal, body_container);
|
||||
description_label = context.create_element<Label>(LabelStyle::Normal, body_container);
|
||||
authors_label = context.create_element<Label>(LabelStyle::Normal, body_container);
|
||||
}
|
||||
|
||||
spacer_element = new Element(this);
|
||||
spacer_element = context.create_element<Element>(this);
|
||||
spacer_element->set_flex(1.0f, 0.0f);
|
||||
|
||||
buttons_container = new Container(FlexDirection::Row, JustifyContent::SpaceAround, this);
|
||||
buttons_container = context.create_element<Container>(FlexDirection::Row, JustifyContent::SpaceAround, this);
|
||||
buttons_container->set_flex(0.0f, 0.0f);
|
||||
buttons_container->set_padding(16.0f);
|
||||
{
|
||||
enable_toggle = new Toggle(buttons_container);
|
||||
enable_toggle = context.create_element<Toggle>(buttons_container);
|
||||
enable_toggle->add_checked_callback(std::bind(&ModDetailsPanel::enable_toggle_checked, this, std::placeholders::_1));
|
||||
configure_button = new Button("Configure", recompui::ButtonStyle::Secondary, buttons_container);
|
||||
erase_button = new Button("Erase", recompui::ButtonStyle::Secondary, buttons_container);
|
||||
configure_button = context.create_element<Button>("Configure", recompui::ButtonStyle::Secondary, buttons_container);
|
||||
erase_button = context.create_element<Button>("Erase", recompui::ButtonStyle::Secondary, buttons_container);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,23 +30,25 @@ ModEntry::ModEntry(Element *parent, const recomp::mods::ModDetails &details, uin
|
||||
set_background_color(Color{ 242, 242, 242, 12 });
|
||||
set_cursor(Cursor::Pointer);
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
{
|
||||
thumbnail_image = new Image(this);
|
||||
thumbnail_image = context.create_element<Image>(this);
|
||||
thumbnail_image->set_width(100.0f);
|
||||
thumbnail_image->set_height(100.0f);
|
||||
thumbnail_image->set_min_width(100.0f);
|
||||
thumbnail_image->set_min_height(100.0f);
|
||||
thumbnail_image->set_background_color(Color{ 190, 184, 219, 25 });
|
||||
|
||||
body_container = new Container(FlexDirection::Column, JustifyContent::FlexStart, this);
|
||||
body_container = context.create_element<Container>(FlexDirection::Column, JustifyContent::FlexStart, this);
|
||||
body_container->set_width_auto();
|
||||
body_container->set_height(100.0f);
|
||||
body_container->set_margin_left(16.0f);
|
||||
body_container->set_overflow(Overflow::Hidden);
|
||||
|
||||
{
|
||||
name_label = new Label(details.mod_id, LabelStyle::Normal, body_container);
|
||||
description_label = new Label("Short description of mod here.", LabelStyle::Small, body_container);
|
||||
name_label = context.create_element<Label>(details.mod_id, LabelStyle::Normal, body_container);
|
||||
description_label = context.create_element<Label>("Short description of mod here.", LabelStyle::Small, body_container);
|
||||
} // body_container
|
||||
} // this
|
||||
}
|
||||
@ -92,13 +94,15 @@ void ModMenu::mod_toggled(bool enabled) {
|
||||
}
|
||||
|
||||
void ModMenu::create_mod_list() {
|
||||
ContextId context = get_current_context();
|
||||
|
||||
// Clear the contents of the list scroll.
|
||||
list_scroll_container->clear_children();
|
||||
mod_entries.clear();
|
||||
|
||||
// Create the child elements for the list scroll.
|
||||
for (size_t mod_index = 0; mod_index < mod_details.size(); mod_index++) {
|
||||
mod_entries.emplace_back(new ModEntry(list_scroll_container, mod_details[mod_index], mod_index, this));
|
||||
mod_entries.emplace_back(context.create_element<ModEntry>(list_scroll_container, mod_details[mod_index], mod_index, this));
|
||||
}
|
||||
|
||||
set_active_mod(0);
|
||||
@ -107,6 +111,8 @@ void ModMenu::create_mod_list() {
|
||||
ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
game_mod_id = "mm";
|
||||
|
||||
ContextId context = get_current_context();
|
||||
|
||||
set_display(Display::Flex);
|
||||
set_flex(1.0f, 1.0f, 100.0f);
|
||||
set_flex_direction(FlexDirection::Column);
|
||||
@ -116,12 +122,12 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
set_height(100.0f, Unit::Percent);
|
||||
|
||||
{
|
||||
body_container = new Container(FlexDirection::Row, JustifyContent::FlexStart, this);
|
||||
body_container = context.create_element<Container>(FlexDirection::Row, JustifyContent::FlexStart, this);
|
||||
body_container->set_flex(1.0f, 1.0f, 100.0f);
|
||||
body_container->set_width(100.0f, Unit::Percent);
|
||||
body_container->set_height(100.0f, Unit::Percent);
|
||||
{
|
||||
list_container = new Container(FlexDirection::Column, JustifyContent::Center, body_container);
|
||||
list_container = context.create_element<Container>(FlexDirection::Column, JustifyContent::Center, body_container);
|
||||
list_container->set_display(Display::Block);
|
||||
list_container->set_flex_basis(100.0f);
|
||||
list_container->set_align_items(AlignItems::Center);
|
||||
@ -129,15 +135,15 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
list_container->set_background_color(Color{ 0, 0, 0, 89 });
|
||||
list_container->set_border_bottom_left_radius(16.0f);
|
||||
{
|
||||
list_scroll_container = new ScrollContainer(ScrollDirection::Vertical, list_container);
|
||||
list_scroll_container = context.create_element<ScrollContainer>(ScrollDirection::Vertical, list_container);
|
||||
} // list_container
|
||||
|
||||
mod_details_panel = new ModDetailsPanel(body_container);
|
||||
mod_details_panel = context.create_element<ModDetailsPanel>(body_container);
|
||||
mod_details_panel->set_mod_toggled_callback(std::bind(&ModMenu::mod_toggled, this, std::placeholders::_1));
|
||||
} // body_container
|
||||
|
||||
|
||||
footer_container = new Container(FlexDirection::Row, JustifyContent::SpaceBetween, this);
|
||||
footer_container = context.create_element<Container>(FlexDirection::Row, JustifyContent::SpaceBetween, this);
|
||||
footer_container->set_width(100.0f, recompui::Unit::Percent);
|
||||
footer_container->set_align_items(recompui::AlignItems::Center);
|
||||
footer_container->set_background_color(Color{ 0, 0, 0, 89 });
|
||||
@ -147,7 +153,7 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
footer_container->set_border_bottom_left_radius(16.0f);
|
||||
footer_container->set_border_bottom_right_radius(16.0f);
|
||||
{
|
||||
refresh_button = new Button("Refresh", recompui::ButtonStyle::Primary, footer_container);
|
||||
refresh_button = context.create_element<Button>("Refresh", recompui::ButtonStyle::Primary, footer_container);
|
||||
refresh_button->add_pressed_callback(std::bind(&ModMenu::refresh_mods, this));
|
||||
} // footer_container
|
||||
} // this
|
||||
@ -165,7 +171,8 @@ ElementModMenu::ElementModMenu(const Rml::String &tag) : Rml::Element(tag) {
|
||||
SetProperty("height", "100%");
|
||||
|
||||
recompui::Element this_compat(this);
|
||||
mod_menu = std::make_unique<ModMenu>(&this_compat);
|
||||
recompui::ContextId context = get_current_context();
|
||||
context.create_element<ModMenu>(&this_compat);
|
||||
}
|
||||
|
||||
ElementModMenu::~ElementModMenu() {
|
||||
|
@ -1471,6 +1471,8 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
|
||||
void deinit_hook() {
|
||||
recompui::destroy_all_contexts();
|
||||
|
||||
std::lock_guard lock {ui_context_mutex};
|
||||
Rml::Debugger::Shutdown();
|
||||
Rml::Shutdown();
|
||||
|
Loading…
x
Reference in New Issue
Block a user