Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions recompui/src/api/ui_api_events.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,7 @@
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"

// TODO: Forced game includes
#include "../../../../../patches/ui_funcs.h"
#include "event_structs.h"

struct QueuedCallback {
recompui::ResourceId resource;
Expand Down
11 changes: 11 additions & 0 deletions recompui/src/base/ui_game_option.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,13 @@ namespace recompui {
}

void GameOption::process_event(const Event &e) {
if (event_callback != nullptr) {
bool cancel = event_callback(e);
if (cancel) {
return;
}
}

switch (e.type) {
case EventType::Click:
if (is_enabled()) {
Expand DownExpand Up@@ -116,4 +123,8 @@ namespace recompui {
void GameOption::set_callback(std::function<void()> new_callback) {
callback = new_callback;
}

void GameOption::set_event_callback(std::function<bool(const Event& e)> new_callback) {
event_callback = new_callback;
}
}
2 changes: 2 additions & 0 deletions recompui/src/base/ui_game_option.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ namespace recompui {
private:
std::string title;
std::function<void()> callback;
std::function<bool(const Event& e)> event_callback = nullptr;
protected:
Label *label;
void set_styles();
Expand All@@ -19,6 +20,7 @@ namespace recompui {
GameOption(ResourceId rid, Element* parent, const std::string& title, std::function<void()> callback, GameOptionsMenuLayout layout);
void set_title(const std::string& new_title);
void set_callback(std::function<void()> new_callback);
void set_event_callback(std::function<bool(const Event& e)> new_callback);
Label* get_label() { return label; }
// These are public so you can modify them.
Style hover_style;
Expand Down
3 changes: 3 additions & 0 deletions recompui/src/composites/ui_mod_menu.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -766,6 +766,9 @@ void update_mod_list(bool scan_mods) {
}

voidupdate_game_mod_id(const std::string &game_mod_id) {
if (current_game_mod_id != "" && game_mod_id != current_game_mod_id && mod_menu) {
mod_menu->set_mods_dirty(false);
}
current_game_mod_id = game_mod_id;
}

Expand Down
100 changes: 57 additions & 43 deletions recompui/src/elements/ui_element.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -671,11 +671,6 @@ Element *Element::get_nav_parent() {
void Element::set_as_navigation_container(NavigationType nav_type) {
is_nav_container = true;
this->nav_type = nav_type;

Element *parent_nav = get_nav_parent();
if (parent_nav != nullptr) {
parent_nav->nav_children.push_back(this);
}
}

void Element::set_nav_wrapping(bool wrapping) {
Expand DownExpand Up@@ -710,53 +705,72 @@ Element::CanFocus Element::is_focusable() {
return CanFocus::No;
}

void Element::get_all_focusable_children(Element *nav_parent) {
for (auto child : children) {
CanFocus res = child->is_focusable();
if (res == CanFocus::Yes) {
nav_parent->nav_children.push_back(child);
} else if (res == CanFocus::NoAndNoChildren) {
continue; // Skip this child, it has no focusable children.
} else {
child->get_all_focusable_children(nav_parent);
}
}
}

// Dive into the hierarchy to build a list of focusable elements and navigation containers.
bool Element::is_distant_parent_of(Element *el) {
if (el == nullptr) {
return false;
}
Element *cur_parent = el->parent;
while (cur_parent != nullptr) {
if (this == cur_parent) {
return true;
}
cur_parent = cur_parent->parent;
}
return false;
}

/**
* - Element should be visible in order for it or any of its children to be considered for navigation.
* - The current focused element is always treated as a valid element. No need to dive into it. If it was focused,
* it is not a nav container.
* - The document calls this function with nav_parent being itself
* - Nav containers' nav_children dont need to be direct descendants, but once you hit a child that:
* - isnt visible
* - focus is NoAndNoChildren
* - is either focusable OR a nav container
* then you stop/continue. if focusable or a nav container, push it to the nav_parent's children
* - One exception, if this element is a parent or distant parent (like mine), then continue diving into that tree.
*/
void Element::build_navigation(Element *nav_parent, Element *cur_focus_element) {
if (!base->IsVisible()) {
return;
bool is_visible = base->IsVisible();
bool is_current_focus = cur_focus_element ? this->id == cur_focus_element->id : false;
bool is_doc = this->get_type_name() == "Document";

if (nav_children.size() > 0) {
nav_children.clear();
}

for (auto &child : children) {
if (child == cur_focus_element) {
nav_parent->nav_children.push_back(child);
continue;
Element::CanFocus can_focus = Element::CanFocus::No;
if (!is_doc) {
// Current focused element doesn't need to be visible or
// focusable, it could have changed either way.
if (is_current_focus) {
nav_parent->nav_children.push_back(this);
return;
}
if (!child->base->IsVisible() || !child->enabled) {
continue;
can_focus = is_focusable();
// All other elements that aren't visible should be skipped.
if (can_focus == Element::CanFocus::NoAndNoChildren && !is_distant_parent_of(cur_focus_element)) {
return;
}
}

if ((child->is_focusable() == CanFocus::Yes) || child->is_nav_container) {
nav_parent->nav_children.push_back(child);
if (can_focus == Element::CanFocus::Yes) {
nav_parent->nav_children.push_back(this);
} else if (is_nav_container) {
// Add nav children recursively to self.
for (auto &child : children) {
child->build_navigation(this, cur_focus_element);
}

if (child->is_nav_container) {
child->nav_children.clear();
child->build_navigation(child, cur_focus_element);

// didn't find any nav children, check for focus elements
if (child->nav_children.size() == 0) {
child->get_all_focusable_children(child);
}

// didn't find any focus elements
if (child->nav_children.size() == 0) {
nav_parent->nav_children.pop_back();
}
// Only give the nav parent this nav container if it has any focusable elements.
if (nav_children.size() > 0) {
nav_parent->nav_children.push_back(this);
}
else {
} else {
// This isn't focusable or a nav container, so iter children recursively from the
// current nav_parent.
for (auto &child : children) {
child->build_navigation(nav_parent, cur_focus_element);
}
}
Expand Down
2 changes: 1 addition & 1 deletion recompui/src/elements/ui_element.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,8 @@ class Element : public Style, public Rml::EventListener {
void ProcessEvent(Rml::Event &event) override final;

Element *get_nav_parent();
void get_all_focusable_children(Element *nav_parent);
void build_navigation(Element *nav_parent, Element *cur_focus_element);
bool is_distant_parent_of(Element *el);
protected:
// Use of this method in inherited classes is discouraged unless it's necessary.
void set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value);
Expand Down
5 changes: 4 additions & 1 deletion recompui/src/elements/ui_modal.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,6 +128,8 @@ void TabbedModal::open() {
if (tabs != nullptr) {
tabs->focus_on_active_tab();
on_tab_change(tabs->get_active_tab());
refresh_tab = true;
queue_update();
}
}

Expand DownExpand Up@@ -334,12 +336,13 @@ TabbedModal::TabbedModal(
void TabbedModal::process_event(const Event &e) {
switch (e.type) {
case EventType::Update: {
if (previous_tab_index != current_tab_index) {
if (refresh_tab || previous_tab_index != current_tab_index) {
body->clear_children();
if (current_tab_index >= 0 && current_tab_index < tab_contexts.size()) {
tab_contexts[current_tab_index].create_contents(modal_root_context, body);
}
previous_tab_index = current_tab_index;
refresh_tab = false;
}
queue_update();
break;
Expand Down
1 change: 1 addition & 0 deletions recompui/src/elements/ui_modal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -102,6 +102,7 @@ namespace recompui {
TabSet *tabs = nullptr;
int previous_tab_index = -1;
int current_tab_index = -1;
bool refresh_tab = false;
virtualvoidprocess_event(const Event &e) override;
std::string_view get_type_name() override { return"TabbedModal"; }
voidon_tab_change(int tab_index);
Expand Down
129 changes: 129 additions & 0 deletions recompui/src/elements/ui_style.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -795,6 +795,135 @@ namespace recompui {
set_property(Rml::PropertyId::Focus, focusable ? Rml::Style::Focus::Auto : Rml::Style::Focus::None);
}

const std::unordered_map<Style::DecoratorType, const std::string> Style::decorator_type_names = {
{Style::DecoratorType::TiledHorizontal , "tiled-horizontal"},
{Style::DecoratorType::TiledVertical , "tiled-vertical"},
{Style::DecoratorType::TiledBox , "tiled-box"},
{Style::DecoratorType::Image , "image"},
{Style::DecoratorType::NinePatch , "ninepatch"},
{Style::DecoratorType::Gradient , "gradient"},
{Style::DecoratorType::HorizontalGradient , "horizontal-gradient"},
{Style::DecoratorType::VerticalGradient , "vertical-gradient"},
{Style::DecoratorType::Shader , "shader"},
{Style::DecoratorType::LinearGradient , "linear-gradient"},
{Style::DecoratorType::RepeatingLinearGradient , "repeating-linear-gradient"},
{Style::DecoratorType::RadialGradient , "radial-gradient"},
{Style::DecoratorType::RepeatingRadialGradient , "repeating-radial-gradient"},
{Style::DecoratorType::ConicGradient , "conic-gradient"},
{Style::DecoratorType::RepeatingConicGradient , "repeating-conic-gradient"},
};

Rml::DecoratorsPtr Style::get_existing_decorators() {
if (property_map.find(Rml::PropertyId::Decorator) != property_map.end()) {
auto cur_decorators = property_map[Rml::PropertyId::Decorator].Get<Rml::DecoratorsPtr>();
if (cur_decorators != nullptr) {
return cur_decorators;
}
}

return nullptr;
}

Rml::DecoratorInstancer* Style::get_decorator_instancer(DecoratorType decorator_type) {
std::string decorator_type_name = decorator_type_names.at(decorator_type);
return Rml::Factory::GetDecoratorInstancer(decorator_type_name);
}

void Style::set_decorator(DecoratorType decorator_type, Rml::PropertyDictionary properties) {
Rml::DecoratorDeclarationList decorators;
Rml::DecoratorsPtr existing_decorator = get_existing_decorators();
bool found_existing = false;
std::string decorator_type_name = decorator_type_names.at(decorator_type);

// BoxArea::Padding is rmlui default but we default containers to Border, so matching for now.
Rml::BoxArea paint_area = Rml::BoxArea::Border;

Rml::DecoratorInstancer* instancer = Rml::Factory::GetDecoratorInstancer(decorator_type_name);

if (existing_decorator != nullptr) {
auto& existing_decorators = existing_decorator->list;
for (int i = 0; i < existing_decorators.size(); i++) {
if (existing_decorators[i].type == decorator_type_name) {
decorators.list.emplace_back(Rml::DecoratorDeclaration{
existing_decorators[i].type,
existing_decorators[i].instancer,
std::move(properties),
paint_area
});
found_existing = true;
} else {
decorators.list.emplace_back(existing_decorators[i]);
}
}
}

if (!found_existing) {
const Rml::PropertySpecification& specification = instancer->GetPropertySpecification();
specification.SetPropertyDefaults(properties);
decorators.list.emplace_back(Rml::DecoratorDeclaration{
decorator_type_name,
instancer,
std::move(properties),
paint_area
});
}

set_property(
Rml::PropertyId::Decorator,
Rml::Property(
Rml::Variant(Rml::MakeShared<Rml::DecoratorDeclarationList>((std::move(decorators)))),
Rml::Unit::DECORATOR
)
);
}

Rml::PropertyDictionary Style::create_decorator_properties(
Style::DecoratorType decorator_type,
std::initializer_list<std::pair<const Rml::String, Rml::Property>> props_to_set,
bool set_defaults
) {
Rml::PropertyDictionary properties;
Rml::DecoratorInstancer* instancer = get_decorator_instancer(decorator_type);
const Rml::PropertySpecification &prop_spec = instancer->GetPropertySpecification();
for (const auto& prop : props_to_set) {
properties.SetProperty(prop_spec.GetProperty(prop.first)->GetId(), prop.second);
}
if (set_defaults) {
prop_spec.SetPropertyDefaults(properties);
}
return properties;
}

void Style::set_decorator_horizontal_gradient(const Color &color_left, const Color &color_right) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::HorizontalGradient, {
{ "start-color", color_left.to_rml_property() },
{ "stop-color", color_right.to_rml_property() }
});

set_decorator(DecoratorType::HorizontalGradient, properties);
}
void Style::set_decorator_horizontal_gradient(recompui::theme::color color_left, recompui::theme::color color_right, int opacity_left, int opacity_right) {
set_decorator_horizontal_gradient(
get_theme_color_with_opacity(color_left, opacity_left),
get_theme_color_with_opacity(color_right, opacity_right)
);
}

void Style::set_decorator_vertical_gradient(const Color &color_top, const Color &color_bottom) {
Rml::PropertyDictionary properties = create_decorator_properties(DecoratorType::VerticalGradient, {
{ "start-color", color_top.to_rml_property() },
{ "stop-color", color_bottom.to_rml_property() }
});

set_decorator(DecoratorType::VerticalGradient, properties);
}
void Style::set_decorator_vertical_gradient(recompui::theme::color color_top, recompui::theme::color color_bottom, int opacity_left, int opacity_right) {
set_decorator_vertical_gradient(
get_theme_color_with_opacity(color_top, opacity_left),
get_theme_color_with_opacity(color_bottom, opacity_right)
);
}

Rml::TransformPtr Style::get_existing_transform() {
if (property_map.find(Rml::PropertyId::Transform) != property_map.end()) {
auto curTransform = property_map[Rml::PropertyId::Transform].Get<Rml::TransformPtr>();
Expand Down
Loading