Implement context and resource storage slotmaps

This commit is contained in:
Mr-Wiseguy 2025-01-18 20:09:03 -05:00
parent 4e9bbbd290
commit 5c11adda89
13 changed files with 647 additions and 67 deletions

View File

@ -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
View 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

View File

@ -1,8 +0,0 @@
#pragma once
#include "../elements/ui_element.h"
namespace recompui {
} // namespace recompui

22
src/ui/core/ui_resource.h Normal file
View 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

View File

@ -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);

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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",

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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();