rodecomp/Source/UI/Text/UITextViewerLinks.cpp
ragnarok-clanker 9953d967a3 Decompile fn_11212 (UITextViewer::AddUrlLinks)
Batch #1848 by ragnarok-clanker: 0 byte-identical, 1 confirmed by peer review.

- fn_11212 UITextViewer::AddUrlLinks: logic confirmed by peer review

Peer-reviewed-by: Yommy
Reviewed-by: ragnarok-clanker
2026-10-28 23:08:43 +00:00

354 lines
12 KiB
C++

#include "Contents/Items/ItemNaming.h"
#include "Render/Text/TextContext.h"
#include "UI/Text/ItemLinkDisplay.h"
#include "UI/Text/NavigationLink.h"
#include "UI/Text/PixelTextWrapping.h"
#include "UI/Text/TextMarkup.h"
#include "UI/Text/TextWrapping.h"
#include "UI/Lists/UIScrollBar.h"
#include "UI/Buttons/UITextButton.h"
#include "UI/Text/UITextViewer.h"
#include <cstring>
// INLINED
std::string UITextViewer::ConvertChat(std::string text) {
std::string converted;
DisplayNavigationLink(converted, text);
const auto* owners = this->m_text->chatConversions;
if (owners) {
for (std::size_t index = 0; index < owners->size(); ++index) {
converted = (*owners)[0]->Convert(converted);
}
}
return converted;
}
// OFFSET: 0x0089C900
// FUNCTION: fn_11589
std::pair<int, int> UITextViewer::LinkPosition(unsigned position, unsigned addedLines) {
unsigned accumulated = 0;
const auto count = static_cast<unsigned>(this->lines.size());
const unsigned first = count - addedLines;
auto& owner = this->m_text->list;
for (unsigned index = first; index < count; ++index) {
const std::string plain = RemoveTextColors(this->lines[index].c_str(), owner.codePage);
accumulated += static_cast<unsigned>(plain.size());
if (accumulated > position) {
const unsigned bytes = position + static_cast<unsigned>(plain.size()) - accumulated;
const std::string prefix = plain.substr(0, bytes);
return {static_cast<int>(index), this->TextWidth(owner.fonts, owner.locale, prefix.c_str(), 0, 0, 12, 0, 0) + 2};
}
}
return {0, 0};
}
// OFFSET: 0x008760D0
// FUNCTION: fn_11205
bool UITextViewer::AddItemLinks(std::string text, unsigned addedLines) {
auto& owner = this->m_text->list;
std::string parsed = RemoveTextColors(text.c_str(), owner.codePage);
parsed = DisplayTextTag(std::move(parsed), "<URL>", "</URL>");
parsed = DisplayTextTag(std::move(parsed), "<NAVI>", "</NAVI>");
if (this->m_text->chatConversions) {
parsed = this->ConvertChat(std::move(parsed));
}
std::string navigation;
DisplayNavigationLink(navigation, parsed);
parsed = std::move(navigation);
parsed = DisplayTextTag(std::move(parsed), "<TIPBOX>", "</TIPBOX>");
std::vector<TextLink> extracted;
ExtractTextLinks(parsed, "<ITEM>", "</ITEM>", extracted);
if (extracted.empty()) {
return false;
}
for (const auto& link : extracted) {
auto* button = new UITextButton(*this->m_context, this->m_text->buttons);
button->Configure(link.display.c_str(), 0x00F4934A, 0, 12, 0, 1);
const int height = button->geometry.height;
const int width = this->TextWidth(owner.fonts, owner.locale, link.display.c_str(), 0, 0, 12, 0, 0);
button->Resize(width + 1, height);
button->m_hoverColor = 0x00FF7864;
button->SetId(464);
SplitTextLinkArguments(link.metadata, button->m_lines);
const auto position = this->LinkPosition(link.position, addedLines);
button->m_value = position.first;
button->Move(position.second, -100);
this->AddChild(button);
this->links.push_back(button);
}
this->SendMsg(nullptr, 7, 0, 0, 0, 0);
return true;
}
// OFFSET: 0x00876700
// FUNCTION: fn_11206
bool UITextViewer::AddNaviLinks(std::string text, unsigned addedLines) {
auto& owner = this->m_text->list;
std::string parsed = RemoveTextColors(text.c_str(), owner.codePage);
parsed = DisplayTextTag(std::move(parsed), "<URL>", "</URL>");
parsed = DisplayTextTag(std::move(parsed), "<ITEM>", "</ITEM>");
if (this->m_text->chatConversions) {
parsed = this->ConvertChat(std::move(parsed));
}
parsed = DisplayTextTag(std::move(parsed), "<TIPBOX>", "</TIPBOX>");
std::string navigation;
DisplayNavigationLink(navigation, parsed);
parsed = std::move(navigation);
parsed = DisplayTextTag(std::move(parsed), "<QUEST>", "</QUEST>");
std::vector<TextLink> extracted;
ExtractTextLinks(parsed, "<NAVI>", "</NAVI>", extracted);
if (extracted.empty()) {
return false;
}
for (const auto& link : extracted) {
auto* button = new UITextButton(*this->m_context, this->m_text->buttons);
button->Configure(link.display.c_str(), 0x00F4934A, 0, 12, 0, 1);
const int height = button->geometry.height;
const int width = this->TextWidth(owner.fonts, owner.locale, link.display.c_str(), 0, 0, 12, 0, 0);
button->Resize(width + 1, height);
button->m_hoverColor = 0x00FF7864;
button->SetId(438);
SplitTextLinkArguments(link.metadata, button->m_lines);
const auto position = this->LinkPosition(link.position, addedLines);
button->m_value = position.first;
button->Move(position.second, -100);
this->AddChild(button);
this->links.push_back(button);
}
this->SendMsg(nullptr, 7, 0, 0, 0, 0);
return true;
}
// OFFSET: 0x00877020
// FUNCTION: fn_11209
bool UITextViewer::AddQuestLinks(std::string text, unsigned addedLines) {
auto& owner = this->m_text->list;
std::string parsed = RemoveTextColors(text.c_str(), owner.codePage);
parsed = DisplayTextTag(std::move(parsed), "<URL>", "</URL>");
parsed = DisplayTextTag(std::move(parsed), "<NAVI>", "</NAVI>");
if (this->m_text->chatConversions) {
parsed = this->ConvertChat(std::move(parsed));
}
std::string navigation;
DisplayNavigationLink(navigation, parsed);
parsed = std::move(navigation);
parsed = DisplayTextTag(std::move(parsed), "<TIPBOX>", "</TIPBOX>");
std::vector<TextLink> extracted;
ExtractTextLinks(parsed, "<QUEST>", "</QUEST>", extracted);
if (extracted.empty()) {
return false;
}
for (const auto& link : extracted) {
auto* button = new UITextButton(*this->m_context, this->m_text->buttons);
button->Configure(link.display.c_str(), 0x00F4934A, 0, 12, 0, 1);
const int height = button->geometry.height;
const int width = this->TextWidth(owner.fonts, owner.locale, link.display.c_str(), 0, 0, 12, 0, 0);
button->Resize(width + 1, height);
button->m_hoverColor = 0x00FF7864;
button->SetId(539);
SplitTextLinkArguments(link.metadata, button->m_lines);
const auto position = this->LinkPosition(link.position, addedLines);
button->m_value = position.first;
button->Move(position.second, -100);
this->AddChild(button);
this->links.push_back(button);
}
this->SendMsg(nullptr, 7, 0, 0, 0, 0);
return true;
}
// OFFSET: 0x00878750
// OFFSET: 0x00878750
// FUNCTION: fn_11212
bool UITextViewer::AddUrlLinks(std::string text, unsigned addedLines) {
auto& owner = this->m_text->list;
std::string parsed = RemoveTextColors(text.c_str(), owner.codePage);
parsed = DisplayTextTag(std::move(parsed), "<NAVI>", "</NAVI>");
parsed = DisplayTextTag(std::move(parsed), "<ITEM>", "</ITEM>");
if (this->m_text->chatConversions) {
parsed = this->ConvertChat(std::move(parsed));
}
parsed = DisplayTextTag(std::move(parsed), "<TIPBOX>", "</TIPBOX>");
std::string navigation;
DisplayNavigationLink(navigation, parsed);
parsed = std::move(navigation);
parsed = DisplayTextTag(std::move(parsed), "<QUEST>", "</QUEST>");
std::vector<TextLink> extracted;
ExtractTextLinks(parsed, "<URL>", "</URL>", extracted);
if (extracted.empty()) {
return false;
}
for (const auto& link : extracted) {
auto* button = new UITextButton(*this->m_context, this->m_text->buttons);
button->Configure(link.display.c_str(), 0x00E6A300, 0, 12, 0, 1);
button->m_hoverColor = 0x00EFB700;
button->SetId(437);
const int height = button->geometry.height;
const int width = this->TextWidth(owner.fonts, owner.locale, link.display.c_str(), 0, 0, 12, 0, 0);
button->Resize(width + 1, height);
parsed = RemoveTextLinkInfo(parsed);
const auto position = this->LinkPosition(link.position, addedLines);
button->m_value = position.first;
button->Move(position.second, -100);
this->AddChild(button);
SplitTextLinkArguments(link.metadata, button->m_lines);
this->links.push_back(button);
}
this->SendMsg(nullptr, 7, 0, 0, 0, 0);
return true;
}
// INLINED
void UITextViewer::AddLinks(const char* text, unsigned addedLines, int kind) {
switch (kind) {
case 0:
this->AddItemLinks(text, addedLines);
return;
case 1:
this->AddUrlLinks(text, addedLines);
return;
case 2:
this->AddNaviLinks(text, addedLines);
return;
case 4:
this->AddQuestLinks(text, addedLines);
return;
default:
break;
}
auto& owner = this->m_text->list;
std::string parsed = RemoveTextColors(text, owner.codePage);
if (kind != 1) {
parsed = DisplayTextTag(std::move(parsed), "<URL>", "</URL>");
}
if (kind != 2) {
parsed = DisplayTextTag(std::move(parsed), "<NAVI>", "</NAVI>");
}
if (kind == 1 || kind == 2 || kind == 3) {
parsed = DisplayTextTag(std::move(parsed), "<ITEM>", "</ITEM>");
}
if (kind == 3) {
parsed = DisplayTextTag(std::move(parsed), "<QUEST>", "</QUEST>");
}
if (this->m_text->chatConversions) {
parsed = this->ConvertChat(std::move(parsed));
}
if (kind == 1 || kind == 2) {
parsed = DisplayTextTag(std::move(parsed), "<TIPBOX>", "</TIPBOX>");
}
std::string navigation;
DisplayNavigationLink(navigation, parsed);
parsed = std::move(navigation);
if (kind == 0 || kind == 4) {
parsed = DisplayTextTag(std::move(parsed), "<TIPBOX>", "</TIPBOX>");
}
if (kind == 1 || kind == 2) {
parsed = DisplayTextTag(std::move(parsed), "<QUEST>", "</QUEST>");
}
const char* begins[] = {"<ITEM>", "<URL>", "<NAVI>", "<TIPBOX>", "<QUEST>"};
const char* ends[] = {"</ITEM>", "</URL>", "</NAVI>", "</TIPBOX>", "</QUEST>"};
const int ids[] = {464, 437, 438, 517, 539};
std::vector<TextLink> extracted;
ExtractTextLinks(std::move(parsed), begins[kind], ends[kind], extracted);
for (const auto& link : extracted) {
std::vector<std::string> labels;
if (kind == 3 && this->splitTipLinks == 1) {
const char* begin = link.display.c_str();
const char* current = begin;
const char* end = begin + link.display.size();
const char* breakAt = begin;
auto limit = reinterpret_cast<std::uintptr_t>(begin) + 32u - link.position;
while (current < end) {
current = CharNextExA(owner.codePage, current, 0);
while (*current == ' ') {
breakAt = ++current;
}
if (reinterpret_cast<std::uintptr_t>(current) >= limit || *current == '\n') {
if (*current == '\n') {
breakAt = current++;
}
const char* split = breakAt <= begin ? current : breakAt;
labels.emplace_back(begin, split);
begin = split;
limit = reinterpret_cast<std::uintptr_t>(begin) + 32u;
}
}
if (begin < current) {
labels.emplace_back(begin, current);
}
} else {
labels.push_back(link.display);
}
for (unsigned fragment = 0; fragment < labels.size(); ++fragment) {
if (kind == 1) {
labels[fragment] = RemoveTextLinkInfo(std::move(labels[fragment]));
}
auto* button = new UITextButton(*this->m_context, this->m_text->buttons);
button->Configure(labels[fragment].c_str(), kind == 1 ? 0x00E6A300 : 0x00F4934A, 0, 12, 0, 1);
const int height = button->geometry.height;
const int width = this->TextWidth(owner.fonts, owner.locale, labels[fragment].c_str(), 0, 0, 12, 0, 0);
button->Resize(width + 1, height);
button->m_hoverColor = kind == 1 ? 0x00E6A300 : 0x00FF7864;
button->SetId(ids[kind]);
SplitTextLinkArguments(link.metadata, button->m_lines);
const auto position = this->LinkPosition(fragment == 0 ? link.position : 0, addedLines - fragment);
button->m_value = position.first;
button->Move(position.second, -100);
this->AddChild(button);
this->links.push_back(button);
}
}
if (!extracted.empty()) {
this->SendMsg(nullptr, 7, 0, 0, 0, 0);
}
}