diff --git a/src/ui/core/ui_context.cpp b/src/ui/core/ui_context.cpp index ce1b38c..c3f5b3e 100644 --- a/src/ui/core/ui_context.cpp +++ b/src/ui/core/ui_context.cpp @@ -33,7 +33,7 @@ namespace recompui { Rml::ElementDocument* document; Element root_element; std::vector loose_elements; - std::unordered_set to_update; + std::unordered_set to_update; bool captures_input = true; bool captures_mouse = true; Context(Rml::ElementDocument* document) : document(document), root_element(document) {} @@ -309,6 +309,15 @@ void recompui::ContextId::open() { 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()) { diff --git a/src/ui/core/ui_context.h b/src/ui/core/ui_context.h index cd125b2..e0d2eaa 100644 --- a/src/ui/core/ui_context.h +++ b/src/ui/core/ui_context.h @@ -42,6 +42,7 @@ namespace recompui { Element* get_root_element(); void open(); + bool open_if_not_already(); void close(); void process_updates(); diff --git a/src/ui/elements/ui_button.cpp b/src/ui/elements/ui_button.cpp index 1058500..5180009 100644 --- a/src/ui/elements/ui_button.cpp +++ b/src/ui/elements/ui_button.cpp @@ -4,7 +4,7 @@ namespace recompui { - Button::Button(Element *parent, const std::string &text, ButtonStyle style) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable), "button") { + Button::Button(Element *parent, const std::string &text, ButtonStyle style) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable), "button", true) { this->style = style; set_text(text); diff --git a/src/ui/elements/ui_element.cpp b/src/ui/elements/ui_element.cpp index e53cd4b..c60ee1c 100644 --- a/src/ui/elements/ui_element.cpp +++ b/src/ui/elements/ui_element.cpp @@ -1,3 +1,5 @@ +#include "RmlUi/Core/StringUtilities.h" + #include "ui_element.h" #include "../core/ui_context.h" @@ -13,7 +15,7 @@ Element::Element(Rml::Element *base) { this->shim = true; } -Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_class) { +Element::Element(Element* parent, uint32_t events_enabled, Rml::String base_class, bool can_set_text) : can_set_text(can_set_text) { ContextId context = get_current_context(); base_owning = context.get_document()->CreateElement(base_class); @@ -39,6 +41,10 @@ Element::~Element() { void Element::add_child(Element *child) { assert(child != nullptr); + if (can_set_text) { + assert(false && "Elements with settable text cannot have children"); + return; + } children.emplace_back(child); @@ -134,9 +140,11 @@ void Element::ProcessEvent(Rml::Event &event) { context = get_context_from_document(doc); } + bool did_open = false; + // TODO disallow null contexts once the entire UI system has been migrated. if (context != ContextId::null()) { - context.open(); + did_open = context.open_if_not_already(); } // Events that are processed during any phase. @@ -187,7 +195,7 @@ void Element::ProcessEvent(Rml::Event &event) { } } - if (context != ContextId::null()) { + if (context != ContextId::null() && did_open) { context.close(); } } @@ -215,6 +223,24 @@ void Element::clear_children() { children.clear(); } +bool Element::remove_child(ResourceId child) { + bool found = false; + + ContextId context = get_current_context(); + + for (auto it = children.begin(); it != children.end(); ++it) { + Element* cur_child = *it; + if (cur_child->get_resource_id() == child) { + children.erase(it); + context.destroy_resource(cur_child); + found = true; + break; + } + } + + return found; +} + void Element::add_style(Style *style, const std::string_view style_name) { add_style(style, { style_name }); } @@ -247,8 +273,13 @@ bool Element::is_enabled() const { } void Element::set_text(std::string_view text) { - // TODO escape this - base->SetInnerRML(std::string(text)); + if (can_set_text) { + // Escape the string into Rml to prevent element injection. + base->SetInnerRML(Rml::StringUtilities::EncodeRml(std::string(text))); + } + else { + assert(false && "Attempted to set text of an element that cannot have its text set."); + } } std::string Element::get_input_text() { diff --git a/src/ui/elements/ui_element.h b/src/ui/elements/ui_element.h index 0897e72..2dbd3bf 100644 --- a/src/ui/elements/ui_element.h +++ b/src/ui/elements/ui_element.h @@ -34,6 +34,7 @@ private: bool enabled = true; bool disabled_attribute = false; bool disabled_from_parent = false; + bool can_set_text = false; void add_child(Element *child); void register_event_listeners(uint32_t events_enabled); @@ -56,9 +57,11 @@ 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", bool can_set_text = false); virtual ~Element(); void clear_children(); + bool remove_child(ResourceId child); + bool remove_child(Element *child) { remove_child(child->get_resource_id()); } void add_style(Style *style, std::string_view style_name); void add_style(Style *style, const std::initializer_list &style_names); void set_enabled(bool enabled); diff --git a/src/ui/elements/ui_label.cpp b/src/ui/elements/ui_label.cpp index 7dc55db..64a53b8 100644 --- a/src/ui/elements/ui_label.cpp +++ b/src/ui/elements/ui_label.cpp @@ -4,7 +4,7 @@ namespace recompui { - Label::Label(Element *parent, LabelStyle label_style) : Element(parent) { + Label::Label(Element *parent, LabelStyle label_style) : Element(parent, 0U, "div", true) { switch (label_style) { case LabelStyle::Annotation: set_color(Color{ 185, 125, 242, 255 }); diff --git a/src/ui/elements/ui_radio.cpp b/src/ui/elements/ui_radio.cpp index 9d20024..3df2bbb 100644 --- a/src/ui/elements/ui_radio.cpp +++ b/src/ui/elements/ui_radio.cpp @@ -4,7 +4,7 @@ namespace recompui { // RadioOption - RadioOption::RadioOption(Element *parent, std::string_view name, uint32_t index) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable), "label") { + RadioOption::RadioOption(Element *parent, std::string_view name, uint32_t index) : Element(parent, Events(EventType::Click, EventType::Focus, EventType::Hover, EventType::Enable), "label", true) { this->index = index; set_text(name); diff --git a/src/ui/elements/ui_span.cpp b/src/ui/elements/ui_span.cpp index 0cd8ef1..e7bcdde 100644 --- a/src/ui/elements/ui_span.cpp +++ b/src/ui/elements/ui_span.cpp @@ -4,7 +4,7 @@ namespace recompui { - Span::Span(Element *parent) : Element(parent, 0, "span") { + Span::Span(Element *parent) : Element(parent, 0, "span", true) { set_font_style(FontStyle::Normal); } diff --git a/src/ui/elements/ui_style.cpp b/src/ui/elements/ui_style.cpp index 33e2563..d3e09c2 100644 --- a/src/ui/elements/ui_style.cpp +++ b/src/ui/elements/ui_style.cpp @@ -181,6 +181,17 @@ namespace recompui { } + void Style::set_visibility(Visibility visibility) { + switch (visibility) { + case Visibility::Visible: + set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Visible); + break; + case Visibility::Hidden: + set_property(Rml::PropertyId::Visibility, Rml::Style::Visibility::Hidden); + break; + } + } + void Style::set_position(Position position) { switch (position) { case Position::Absolute: diff --git a/src/ui/elements/ui_style.h b/src/ui/elements/ui_style.h index 480ab9e..08fd852 100644 --- a/src/ui/elements/ui_style.h +++ b/src/ui/elements/ui_style.h @@ -20,6 +20,7 @@ namespace recompui { public: Style(); virtual ~Style(); + void set_visibility(Visibility visibility); void set_position(Position position); void set_left(float left, Unit unit = Unit::Dp); void set_top(float top, Unit unit = Unit::Dp); diff --git a/src/ui/elements/ui_types.h b/src/ui/elements/ui_types.h index 9442955..c9ae1bb 100644 --- a/src/ui/elements/ui_types.h +++ b/src/ui/elements/ui_types.h @@ -152,6 +152,11 @@ namespace recompui { TableCell }; + enum class Visibility { + Visible, + Hidden + }; + enum class Position { Absolute, Relative diff --git a/src/ui/ui_api.cpp b/src/ui/ui_api.cpp index 03001bf..7e029da 100644 --- a/src/ui/ui_api.cpp +++ b/src/ui/ui_api.cpp @@ -168,6 +168,25 @@ void recompui_create_element(uint8_t* rdram, recomp_context* ctx) { return_resource(ctx, ret->get_resource_id()); } +void recompui_destroy_element(uint8_t* rdram, recomp_context* ctx) { + Style* parent_resource = arg_style<0>(rdram, ctx); + + if (!parent_resource->is_element()) { + recompui::message_box("Fatal error in mod - attempted to remove child from non-element"); + assert(false); + ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); + } + + Element* parent = static_cast(parent_resource); + ResourceId to_remove = arg_resource_id<1>(rdram, ctx); + + if (!parent->remove_child(to_remove)) { + recompui::message_box("Fatal error in mod - attempted to remove child from wrong parent"); + assert(false); + ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); + } +} + void recompui_create_label(uint8_t* rdram, recomp_context* ctx) { ContextId ui_context = get_context(rdram, ctx); Element* parent = arg_element<1>(rdram, ctx, ui_context); @@ -206,6 +225,13 @@ void recompui_create_button(uint8_t* rdram, recomp_context* ctx) { } // Position and Layout +void recompui_set_visibility(uint8_t* rdram, recomp_context* ctx) { + Style* resource = arg_style<0>(rdram, ctx); + uint32_t visibility = _arg<1, uint32_t>(rdram, ctx); + + resource->set_visibility(static_cast(visibility)); +} + void recompui_set_position(uint8_t* rdram, recomp_context* ctx) { Style* resource = arg_style<0>(rdram, ctx); uint32_t position = _arg<1, uint32_t>(rdram, ctx); @@ -645,6 +671,19 @@ void recompui_set_overflow_y(uint8_t* rdram, recomp_context* ctx) { } // Text and Fonts +void recompui_set_text(uint8_t* rdram, recomp_context* ctx) { + Style* resource = arg_style<0>(rdram, ctx); + + if (!resource->is_element()) { + recompui::message_box("Fatal error in mod - attempted to set text of non-element"); + assert(false); + ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); + } + + Element* element = static_cast(resource); + element->set_text(_arg_string<1>(rdram, ctx)); +} + void recompui_set_font_size(uint8_t* rdram, recomp_context* ctx) { Style* resource = arg_style<0>(rdram, ctx); float size = _arg_float_a1(rdram, ctx); @@ -731,12 +770,12 @@ void recompui_set_tab_index(uint8_t* rdram, recomp_context* ctx) { resource->set_tab_index(static_cast(tab_index)); } -// Getters +// Text void recompui_get_input_text(uint8_t* rdram, recomp_context* ctx) { Style* resource = arg_style<0>(rdram, ctx); if (!resource->is_element()) { - recompui::message_box("Fatal error in mod - attempted to get text of non-element"); + recompui::message_box("Fatal error in mod - attempted to get input text of non-element"); assert(false); ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); } @@ -746,12 +785,11 @@ void recompui_get_input_text(uint8_t* rdram, recomp_context* ctx) { return_string(rdram, ctx, ret); } -// Setters void recompui_set_input_text(uint8_t* rdram, recomp_context* ctx) { Style* resource = arg_style<0>(rdram, ctx); if (!resource->is_element()) { - recompui::message_box("Fatal error in mod - attempted to set text of non-element"); + recompui::message_box("Fatal error in mod - attempted to set input text of non-element"); assert(false); ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); } @@ -798,10 +836,12 @@ void recompui::register_ui_exports() { REGISTER_FUNC(recompui_set_context_captures_mouse); REGISTER_FUNC(recompui_create_style); REGISTER_FUNC(recompui_create_element); + REGISTER_FUNC(recompui_destroy_element); REGISTER_FUNC(recompui_create_label); // REGISTER_FUNC(recompui_create_span); REGISTER_FUNC(recompui_create_textinput); REGISTER_FUNC(recompui_create_button); + REGISTER_FUNC(recompui_set_visibility); REGISTER_FUNC(recompui_set_position); REGISTER_FUNC(recompui_set_left); REGISTER_FUNC(recompui_set_top); @@ -860,6 +900,7 @@ void recompui::register_ui_exports() { REGISTER_FUNC(recompui_set_overflow); REGISTER_FUNC(recompui_set_overflow_x); REGISTER_FUNC(recompui_set_overflow_y); + REGISTER_FUNC(recompui_set_text); REGISTER_FUNC(recompui_set_font_size); REGISTER_FUNC(recompui_set_letter_spacing); REGISTER_FUNC(recompui_set_line_height);