mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp
synced 2025-10-05 16:12:49 +02:00
* init config opt system w/ 3 types and description support * Move config registry/option to librecomp + added Color conf opt type * Updated color option type styling * Added dropdown option type * Added TextField option type * Button config type + callback wip * init mod menu + bem class + button presets * WIP mod menu, fix some warnings * Rewrite mod details under new UI system. * Refactored mods menu entirely. * Remove ModMenu.scss. * Take ownership of created pointers on Element class. * Add styles. * Multi-style state and disabled state propagation. * Switch to string views. * Convert to spaces, hook up mod enabled to toggle. * Mod menu progress. * Layout for mod details panel, add gap property setters * Update RmlUi for gap property in flexbox * Add slot_map and begin ui context * Implement context and resource storage slotmaps * Config submenu. * Refactored to account for context changes. * Turn off tab searching when submenu is open. * Revert accidental RmlUi downgrade * Upgrade RmlUi to 6.0 release * Text input. * Radio option. * Cleanup. * Refactor Rml document handling to use new ContextId system (prompts currently unimplemented) * Add support for config schema. * Split config sub menu into separate context and fix configure button, prevent infinite loop when looking for autofocus element * Reimplement mechanism to open the config menu to a specific tab * Begin implementing mod UI API * Link storage to mod menu. * Proper enum parsing. * Enable mod reordering. * Draggable improvements to mod menu and runtime update. * Adjust styling of submenu. * Mods folder button. * Linux build fixes. * Hook up new manifest fields to mod UI * Add basic thumbnail parsing functionality. * More style changes. * Implement update event for elements * Use RT64's texture laoding instead. * Restore spacer animations. * Animation API begone. * Auto-enabled mods. * Update runtime submodule and N64Recomp commit in CI for mod config API, remove unnecessary extern C * Sub menu display name, assert on text input. * Clamp delta time to fix UI disappearing on OS with timestamps that don't always increase. * Add a state for when no mods are installed. * Unify API function naming scheme and export relevant API functions * Add actor update/init events and save init event (#536) * Expose remaining property setters to mod UI API * Implemented mod UI callbacks * Implement actor extension data and use it for transform tagging * Zero the memory allocated to hold extended actor data * Implement label and textinput in mod UI API * Patch virtual address translation to support entire extended RAM address space (#533) * Download full target build of llvm in CI Windows runners to fix missing MIPS support and update N64Recomp CI commit * Enable triple buffering in RT64 (#546) * Implement controlling input capturing for mod UI contexts * Created mod UI API functions for setting visibility, setting text, and destroying elements * Fix errant RML tag in mod menu and insert breaks for newlines when setting element text * Fix compilation after rebase * Fixes for macOS * Set the blender description manually for the UI renderer * Created mod UI API functions for imageview elements * Switch to designated initializers to work around missing aggregate initialization compiler support * Update RT64 for driver bug workarounds and misc fixes * Update RT64 to fix native sampler issues with tile copies * Update RT64 for depth clear optimization and more native sampler changes * Update RT64 and allow it to choose the graphics API when set to Auto * Update runtime to allow renderers to choose the graphics API * Update RT64 to enable early Z test shader optimization * Implement data structure mod APIs * Update lunasvg to increase its minimum cmake version * Switch to runtime concatenation of function name in data API error reporting to fix Linux compilation issue * Add missing typename to fix compilation on some compilers * Update RT64 to fix failed assert with MSAA off * Reimplement prompts as a separate UI context and make it so the quit game prompt doesn't bring up the config menu * DnD prototype. * Fix to dynamic lib path and runtime commit. * Finish drag and drop mod installation, disable mod refresh button and code mod toggle when game starts * Remove std::format usage and add missing <list> includes to fix Linux/MacOS compilation * Switch to aggregate initialization for Version to work around missing implicit constructor on some compilers * Replace use of std::bind with lambdas * Add mod install button, put mod description in scroll container, minor mod menu tweaks * Update runtime to fix renderer shutdown race condition * Implement texture pack reordering * Add mod UI API exports for slider, password input, and label radio and expose RmlUi debugger on F8 * Update runtime for mod version export * Update runtime for save swapping mod API * Apply recomp.rcss to mod UI contexts (fixes scrolls) * Updated mod list styling (#561) * Updated mod list styling * mod entry max height * Update RT64 for v5 texture hash * Update runtime for mod API to get save file path * Add special config option id to control texture pack state for code mods * Update runtime for mod default enabled state * Add exports for stars' display lists (#563) * Update runtime to fix default value of enabled_by_default * Update runtime to allow NULL recomp_free * Implement navigation and focus styling for new UI framework (no manual overrides yet) * Fix the previous scissor state bleeding when drawing the RmlUi output onto the swapchain buffer * Use a multiple file select dialog for mod install button * Add mod export for loading UI image from bytes (png/dds) * Manual navigation in UI framework and WIP mod menu navigation * Repeat key events when holding down controller inputs for UI navigation * Patch AnimationContext_SetLoadFrame to allow custom animations (#564) * Close context when showing or hiding a context and reopen afterwards to prevent deadlocks * Add quotes around xdg-open and open commands to support paths with spaces * Update RT64 for high precision texture coordinates when using texture replacements * Add support for built-in mods and convert D-Pad to a built-in mod (#567) * Add embedded mod (using mm_recomp_draw_distance as an example). * Update runtime after merge * Experiment with removing the D-Pad. * Add event needed for dpad as mod, revert remaining changes in built-in patches for dpad * Add built-in dpad mod, add remaining event calls to input.c * Add built-in mods readme --------- Co-authored-by: Dario <dariosamo@gmail.com> * Fixing navigation of mods menu. * Focused state for mod entry. * Prevent hover styling and focus on input elements when disabled * Fix up/down navigation on text input elements * Set mod tab to navigate down to first mod, fix redundant mod scanning * Remove more redundant mod scanning and fix mods being scanned during gameplay * Update runtime for mod folder export * Improve radio navigation and setup mod config submenu navigation setup * Restore fd anywhere export functionality (#570) * fix fd * add comment back in * Make config tabset navigate down to first mod entry when mod menu is open, make mod configure screen focus on configure button after closing * Add navigation exports to mod UI API * Fix opening the config menu via keyboard/controller causing a double animation warning in RmlUi --------- Co-authored-by: Dario <dariosamo@gmail.com> Co-authored-by: thecozies <79979276+thecozies@users.noreply.github.com> Co-authored-by: Garrett Cox <garrettjcox@gmail.com> Co-authored-by: David Chavez <david@dcvz.io> Co-authored-by: danielryb <59661841+danielryb@users.noreply.github.com> Co-authored-by: Reonu <danileon95@gmail.com> Co-authored-by: LittleCube <littlecubehax@gmail.com>
669 lines
22 KiB
C++
669 lines
22 KiB
C++
#include <mutex>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <fstream>
|
|
|
|
#include "slot_map.h"
|
|
#include "RmlUi/Core/StreamMemory.h"
|
|
|
|
#include "ultramodern/error_handling.hpp"
|
|
#include "recomp_ui.h"
|
|
#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;
|
|
Element* autofocus_element = nullptr;
|
|
std::vector<Element*> loose_elements;
|
|
std::unordered_set<ResourceId> to_update;
|
|
bool captures_input = true;
|
|
bool captures_mouse = true;
|
|
Context(Rml::ElementDocument* document) : document(document), root_element(document) {}
|
|
};
|
|
} // namespace recompui
|
|
|
|
using context_slotmap = dod::slot_map32<recompui::Context>;
|
|
|
|
static struct {
|
|
std::recursive_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;
|
|
Rml::SharedPtr<Rml::StyleSheetContainer> style_sheet;
|
|
} 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,
|
|
UpdateElementWithoutContext,
|
|
UpdateElementInWrongContext,
|
|
GetResourceWithoutOpen,
|
|
GetResourceFailed,
|
|
DestroyResourceWithoutOpen,
|
|
DestroyResourceInWrongContext,
|
|
DestroyResourceNotFound,
|
|
GetDocumentInvalidContext,
|
|
GetAutofocusInvalidContext,
|
|
SetAutofocusInvalidContext,
|
|
InternalError,
|
|
};
|
|
|
|
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::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;
|
|
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::GetDocumentInvalidContext:
|
|
error_message = "Attempted to get the document of an invalid UI context";
|
|
break;
|
|
case ContextErrorType::GetAutofocusInvalidContext:
|
|
error_message = "Attempted to get the autofocus element of an invalid UI context";
|
|
break;
|
|
case ContextErrorType::SetAutofocusInvalidContext:
|
|
error_message = "Attempted to set the autofocus element 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;
|
|
}
|
|
|
|
// 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.
|
|
recompui::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;
|
|
}
|
|
|
|
void recompui::init_styling(const std::filesystem::path& rcss_file) {
|
|
std::string style{};
|
|
{
|
|
std::ifstream style_stream{rcss_file};
|
|
style_stream.seekg(0, std::ios::end);
|
|
style.resize(style_stream.tellg());
|
|
style_stream.seekg(0, std::ios::beg);
|
|
|
|
style_stream.read(style.data(), style.size());
|
|
}
|
|
std::unique_ptr<Rml::StreamMemory> rml_stream = std::make_unique<Rml::StreamMemory>(reinterpret_cast<Rml::byte*>(style.data()), style.size());
|
|
rml_stream->SetSourceURL(rcss_file.filename().string());
|
|
context_state.style_sheet = Rml::Factory::InstanceStyleSheetStream(rml_stream.get());
|
|
}
|
|
|
|
recompui::ContextId recompui::create_context(const std::filesystem::path& path) {
|
|
ContextId new_context = create_context_impl(nullptr);
|
|
|
|
auto workingdir = std::filesystem::current_path();
|
|
|
|
new_context.open();
|
|
Rml::ElementDocument* doc = recompui::load_document(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);
|
|
}
|
|
|
|
recompui::ContextId recompui::create_context() {
|
|
Rml::ElementDocument* doc = create_empty_document();
|
|
doc->SetStyleSheetContainer(context_state.style_sheet);
|
|
ContextId ret = create_context_impl(doc);
|
|
Element* root = ret.get_root_element();
|
|
// Mark the root element as not being a shim, as that's only needed for elements that were parented to Rml ones manually.
|
|
root->shim = false;
|
|
|
|
ret.open();
|
|
root->set_width(100.0f, Unit::Percent);
|
|
root->set_height(100.0f, Unit::Percent);
|
|
root->set_display(Display::Flex);
|
|
ret.close();
|
|
|
|
doc->Hide();
|
|
|
|
return ret;
|
|
}
|
|
|
|
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() {
|
|
recompui::hide_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;
|
|
}
|
|
|
|
bool recompui::ContextId::open_if_not_already() {
|
|
if (opened_context_id == *this) {
|
|
return false;
|
|
}
|
|
|
|
open();
|
|
return true;
|
|
}
|
|
|
|
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::ContextId recompui::try_close_current_context() {
|
|
if (opened_context_id != ContextId::null()) {
|
|
ContextId prev_context = opened_context_id;
|
|
opened_context_id.close();
|
|
return prev_context;
|
|
}
|
|
return ContextId::null();
|
|
}
|
|
|
|
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())->handle_event(update_event);
|
|
}
|
|
}
|
|
|
|
bool recompui::ContextId::captures_input() {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
return false;
|
|
}
|
|
return ctx->captures_input;
|
|
|
|
}
|
|
|
|
bool recompui::ContextId::captures_mouse() {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
return false;
|
|
}
|
|
return ctx->captures_mouse;
|
|
}
|
|
|
|
void recompui::ContextId::set_captures_input(bool captures_input) {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
return;
|
|
}
|
|
ctx->captures_input = captures_input;
|
|
}
|
|
|
|
void recompui::ContextId::set_captures_mouse(bool captures_mouse) {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
return;
|
|
}
|
|
ctx->captures_mouse = captures_mouse;
|
|
}
|
|
|
|
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) {
|
|
Element* element_ptr = static_cast<Element*>(resource_ptr);
|
|
element_ptr->set_id(std::string{element_ptr->get_type_name()} + "-" + std::to_string(key.raw));
|
|
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));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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>());
|
|
}
|
|
|
|
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() {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
|
}
|
|
|
|
return ctx->document;
|
|
}
|
|
|
|
recompui::Element* recompui::ContextId::get_root_element() {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
|
}
|
|
|
|
return &ctx->root_element;
|
|
}
|
|
|
|
recompui::Element* recompui::ContextId::get_autofocus_element() {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
context_error(*this, ContextErrorType::GetAutofocusInvalidContext);
|
|
}
|
|
|
|
return ctx->autofocus_element;
|
|
}
|
|
|
|
void recompui::ContextId::set_autofocus_element(Element* element) {
|
|
std::lock_guard lock{ context_state.all_contexts_lock };
|
|
|
|
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
|
if (ctx == nullptr) {
|
|
context_error(*this, ContextErrorType::SetAutofocusInvalidContext);
|
|
}
|
|
|
|
ctx->autofocus_element = 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;
|
|
}
|