mirror of
https://github.com/geode-sdk/geode.git
synced 2025-05-18 17:10:59 -04:00
708 lines
24 KiB
C++
708 lines
24 KiB
C++
#include <Geode/binding/ProfilePage.hpp>
|
|
#include <Geode/binding/CCContentLayer.hpp>
|
|
#include <Geode/loader/Mod.hpp>
|
|
#include <Geode/ui/MDTextArea.hpp>
|
|
#include <Geode/utils/casts.hpp>
|
|
#include <Geode/utils/cocos.hpp>
|
|
#include <Geode/utils/web.hpp>
|
|
#include <Geode/utils/ranges.hpp>
|
|
#include <Geode/utils/string.hpp>
|
|
#include <md4c.h>
|
|
|
|
USE_GEODE_NAMESPACE();
|
|
|
|
static constexpr float g_fontScale = .5f;
|
|
static constexpr float g_paragraphPadding = 7.f;
|
|
static constexpr float g_indent = 7.f;
|
|
static constexpr float g_codeBlockIndent = 8.f;
|
|
static constexpr ccColor3B g_linkColor = cc3x(0x7ff4f4);
|
|
|
|
TextRenderer::Font g_mdFont = [](int style) -> TextRenderer::Label {
|
|
if ((style & TextStyleBold) && (style & TextStyleItalic)) {
|
|
return CCLabelBMFont::create("", "mdFontBI.fnt"_spr);
|
|
}
|
|
if ((style & TextStyleBold)) {
|
|
return CCLabelBMFont::create("", "mdFontB.fnt"_spr);
|
|
}
|
|
if ((style & TextStyleItalic)) {
|
|
return CCLabelBMFont::create("", "mdFontI.fnt"_spr);
|
|
}
|
|
return CCLabelBMFont::create("", "mdFont.fnt"_spr);
|
|
};
|
|
|
|
TextRenderer::Font g_mdMonoFont = [](int style) -> TextRenderer::Label {
|
|
return CCLabelBMFont::create("", "mdFontMono.fnt"_spr);
|
|
};
|
|
|
|
class MDContentLayer : public CCContentLayer {
|
|
protected:
|
|
CCMenu* m_content;
|
|
|
|
public:
|
|
static MDContentLayer* create(CCMenu* content, float width, float height) {
|
|
auto ret = new MDContentLayer();
|
|
if (ret && ret->initWithColor({ 0, 255, 0, 0 }, width, height)) {
|
|
ret->m_content = content;
|
|
ret->autorelease();
|
|
return ret;
|
|
}
|
|
CC_SAFE_DELETE(ret);
|
|
return nullptr;
|
|
}
|
|
|
|
void setPosition(CCPoint const& pos) override {
|
|
// cringe CCContentLayer expect its children to
|
|
// all be TableViewCells
|
|
CCLayerColor::setPosition(pos);
|
|
|
|
// so that's why based MDContentLayer expects itself
|
|
// to have a CCMenu :-)
|
|
if (m_content) {
|
|
for (auto child : CCArrayExt<CCNode>(m_content->getChildren())) {
|
|
auto y = this->getPositionY() + child->getPositionY();
|
|
child->setVisible(
|
|
!((m_content->getContentSize().height < y) ||
|
|
(y < -child->getContentSize().height))
|
|
);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Result<ccColor3B> colorForIdentifier(std::string const& tag) {
|
|
if (utils::string::contains(tag, ' ')) {
|
|
auto hexStr = utils::string::split(utils::string::normalize(tag), " ").at(1);
|
|
try {
|
|
auto hex = std::stoi(hexStr, nullptr, 16);
|
|
return Ok(cc3x(hex));
|
|
}
|
|
catch (...) {
|
|
return Err("Invalid hex");
|
|
}
|
|
}
|
|
else {
|
|
auto colorText = tag.substr(1);
|
|
if (!colorText.size()) {
|
|
return Err("No color specified");
|
|
}
|
|
else if (colorText.size() > 1) {
|
|
return Err("Color tag " + tag + " unexpectedly long, either do <cx> or <c hex>");
|
|
}
|
|
else {
|
|
switch (colorText.front()) {
|
|
case 'b': return Ok(cc3x(0x4a52e1)); break;
|
|
case 'g': return Ok(cc3x(0x40e348)); break;
|
|
case 'l': return Ok(cc3x(0x60abef)); break;
|
|
case 'j': return Ok(cc3x(0x32c8ff)); break;
|
|
case 'y': return Ok(cc3x(0xffff00)); break;
|
|
case 'o': return Ok(cc3x(0xffa54b)); break;
|
|
case 'r': return Ok(cc3x(0xff5a5a)); break;
|
|
case 'p': return Ok(cc3x(0xff00ff)); break;
|
|
default: return Err("Unknown color " + colorText);
|
|
}
|
|
}
|
|
}
|
|
return Err("Unknown error");
|
|
}
|
|
|
|
bool MDTextArea::init(std::string const& str, CCSize const& size) {
|
|
if (!CCLayer::init()) return false;
|
|
|
|
m_text = str;
|
|
m_size = size;
|
|
this->setContentSize(size);
|
|
m_renderer = TextRenderer::create();
|
|
CC_SAFE_RETAIN(m_renderer);
|
|
|
|
m_bgSprite = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
|
m_bgSprite->setScale(.5f);
|
|
m_bgSprite->setColor({ 0, 0, 0 });
|
|
m_bgSprite->setOpacity(75);
|
|
m_bgSprite->setContentSize(size * 2 + CCSize { 25.f, 25.f });
|
|
m_bgSprite->setPosition(size / 2);
|
|
this->addChild(m_bgSprite);
|
|
|
|
m_scrollLayer = ScrollLayer::create({ 0, 0, m_size.width, m_size.height }, true);
|
|
|
|
m_content = CCMenu::create();
|
|
m_content->setZOrder(2);
|
|
m_scrollLayer->m_contentLayer->addChild(m_content);
|
|
|
|
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
|
m_scrollLayer->registerWithTouchDispatcher();
|
|
|
|
this->addChild(m_scrollLayer);
|
|
|
|
this->updateLabel();
|
|
|
|
return true;
|
|
}
|
|
|
|
MDTextArea::~MDTextArea() {
|
|
CC_SAFE_RELEASE(m_renderer);
|
|
}
|
|
|
|
class BreakLine : public CCNode {
|
|
protected:
|
|
void draw() override {
|
|
// some nodes sometimes set the blend func to
|
|
// something else without resetting it back
|
|
ccGLBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
ccDrawSolidRect({ 0, 0 }, this->getContentSize(), { 1.f, 1.f, 1.f, .2f });
|
|
CCNode::draw();
|
|
}
|
|
|
|
public:
|
|
static BreakLine* create(float width) {
|
|
auto ret = new BreakLine;
|
|
if (ret && ret->init()) {
|
|
ret->autorelease();
|
|
ret->setContentSize({ width, 1.f });
|
|
return ret;
|
|
}
|
|
CC_SAFE_DELETE(ret);
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
void MDTextArea::onLink(CCObject* pSender) {
|
|
auto href = as<CCString*>(as<CCNode*>(pSender)->getUserObject());
|
|
auto layer = FLAlertLayer::create(
|
|
this, "Hold Up!",
|
|
"Links are spooky! Are you sure you want to go to <cy>" + std::string(href->getCString()) +
|
|
"</c>?",
|
|
"Cancel", "Yes", 360.f
|
|
);
|
|
layer->setUserObject(href);
|
|
layer->show();
|
|
}
|
|
|
|
void MDTextArea::onGDProfile(CCObject* pSender) {
|
|
auto href = as<CCString*>(as<CCNode*>(pSender)->getUserObject());
|
|
auto profile = std::string(href->getCString());
|
|
profile = profile.substr(profile.find(":") + 1);
|
|
try {
|
|
ProfilePage::create(std::stoi(profile), false)->show();
|
|
}
|
|
catch (...) {
|
|
FLAlertLayer::create(
|
|
"Error",
|
|
"Invalid profile ID: <cr>" + profile +
|
|
"</c>. This is "
|
|
"probably the modder's fault, report the bug to them.",
|
|
"OK"
|
|
)
|
|
->show();
|
|
}
|
|
}
|
|
|
|
void MDTextArea::FLAlert_Clicked(FLAlertLayer* layer, bool btn) {
|
|
if (btn) {
|
|
web::openLinkInBrowser(as<CCString*>(layer->getUserObject())->getCString());
|
|
}
|
|
}
|
|
|
|
struct MDParser {
|
|
static std::string s_lastLink;
|
|
static std::string s_lastImage;
|
|
static bool s_isOrderedList;
|
|
static bool s_isCodeBlock;
|
|
static float s_codeStart;
|
|
static size_t s_orderedListNum;
|
|
static std::vector<TextRenderer::Label> s_codeSpans;
|
|
|
|
static int parseText(MD_TEXTTYPE type, MD_CHAR const* rawText, MD_SIZE size, void* mdtextarea) {
|
|
auto textarea = static_cast<MDTextArea*>(mdtextarea);
|
|
auto renderer = textarea->m_renderer;
|
|
auto text = std::string(rawText, size);
|
|
switch (type) {
|
|
case MD_TEXTTYPE::MD_TEXT_CODE:
|
|
{
|
|
auto rendered = renderer->renderString(text);
|
|
if (!s_isCodeBlock) {
|
|
// code span BGs need to be rendered after all
|
|
// rendering is done since the position of the
|
|
// rendered labels may change after alignments
|
|
// are adjusted
|
|
ranges::push(s_codeSpans, rendered);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MD_TEXTTYPE::MD_TEXT_BR:
|
|
{
|
|
renderer->breakLine();
|
|
}
|
|
break;
|
|
|
|
case MD_TEXTTYPE::MD_TEXT_SOFTBR:
|
|
{
|
|
renderer->breakLine();
|
|
}
|
|
break;
|
|
|
|
case MD_TEXTTYPE::MD_TEXT_NORMAL:
|
|
{
|
|
if (s_lastLink.size()) {
|
|
renderer->pushColor(g_linkColor);
|
|
renderer->pushDecoFlags(TextDecorationUnderline);
|
|
auto rendered = renderer->renderStringInteractive(
|
|
text, textarea,
|
|
utils::string::startsWith(s_lastLink, "user:")
|
|
? menu_selector(MDTextArea::onGDProfile)
|
|
: menu_selector(MDTextArea::onLink)
|
|
);
|
|
for (auto const& label : rendered) {
|
|
label.m_node->setUserObject(CCString::create(s_lastLink));
|
|
}
|
|
renderer->popDecoFlags();
|
|
renderer->popColor();
|
|
}
|
|
else if (s_lastImage.size()) {
|
|
bool isFrame = false;
|
|
if (utils::string::startsWith(s_lastImage, "frame:")) {
|
|
s_lastImage = s_lastImage.substr(s_lastImage.find(":") + 1);
|
|
isFrame = true;
|
|
}
|
|
CCSprite* spr = nullptr;
|
|
if (isFrame) {
|
|
spr = CCSprite::createWithSpriteFrameName(s_lastImage.c_str());
|
|
}
|
|
else {
|
|
spr = CCSprite::create(s_lastImage.c_str());
|
|
}
|
|
if (spr) {
|
|
renderer->renderNode(spr);
|
|
}
|
|
else {
|
|
renderer->renderString(text);
|
|
}
|
|
s_lastImage = "";
|
|
}
|
|
else {
|
|
renderer->renderString(text);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MD_TEXTTYPE::MD_TEXT_HTML:
|
|
{
|
|
if (text.size() > 2) {
|
|
auto tag = utils::string::trim(text.substr(1, text.size() - 2));
|
|
auto isClosing = tag.front() == '/';
|
|
if (isClosing) tag = tag.substr(1);
|
|
if (tag.front() != 'c') {
|
|
log::warn("Unknown tag {}", text);
|
|
renderer->renderString(text);
|
|
}
|
|
else {
|
|
if (isClosing) {
|
|
renderer->popColor();
|
|
}
|
|
else {
|
|
auto color = colorForIdentifier(tag);
|
|
if (color) {
|
|
renderer->pushColor(color.unwrap());
|
|
}
|
|
else {
|
|
log::warn("Error parsing color: {}", color.unwrapErr());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log::warn("Too short tag {}", text);
|
|
renderer->renderString(text);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
log::warn("Unhandled text type {}", type);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int enterBlock(MD_BLOCKTYPE type, void* detail, void* mdtextarea) {
|
|
auto textarea = static_cast<MDTextArea*>(mdtextarea);
|
|
auto renderer = textarea->m_renderer;
|
|
switch (type) {
|
|
case MD_BLOCKTYPE::MD_BLOCK_DOC:
|
|
{
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_H:
|
|
{
|
|
auto hdetail = static_cast<MD_BLOCK_H_DETAIL*>(detail);
|
|
renderer->pushStyleFlags(TextStyleBold);
|
|
switch (hdetail->level) {
|
|
case 1: renderer->pushScale(g_fontScale * 2.f); break;
|
|
case 2: renderer->pushScale(g_fontScale * 1.5f); break;
|
|
case 3: renderer->pushScale(g_fontScale * 1.17f); break;
|
|
case 4: renderer->pushScale(g_fontScale); break;
|
|
case 5: renderer->pushScale(g_fontScale * .83f); break;
|
|
default:
|
|
case 6: renderer->pushScale(g_fontScale * .67f); break;
|
|
}
|
|
// switch (hdetail->level) {
|
|
// case 3: renderer->pushCaps(TextCapitalization::AllUpper); break;
|
|
// }
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_P:
|
|
{
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_UL:
|
|
case MD_BLOCKTYPE::MD_BLOCK_OL:
|
|
{
|
|
renderer->pushIndent(g_indent);
|
|
s_isOrderedList = type == MD_BLOCKTYPE::MD_BLOCK_OL;
|
|
s_orderedListNum = 0;
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_HR:
|
|
{
|
|
renderer->breakLine(g_paragraphPadding / 2);
|
|
renderer->renderNode(BreakLine::create(textarea->m_size.width));
|
|
renderer->breakLine(g_paragraphPadding);
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_LI:
|
|
{
|
|
renderer->pushOpacity(renderer->getCurrentOpacity() / 2);
|
|
auto lidetail = static_cast<MD_BLOCK_LI_DETAIL*>(detail);
|
|
if (s_isOrderedList) {
|
|
s_orderedListNum++;
|
|
renderer->renderString(std::to_string(s_orderedListNum) + ". ");
|
|
}
|
|
else {
|
|
renderer->renderString("• ");
|
|
}
|
|
renderer->popOpacity();
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_CODE:
|
|
{
|
|
s_isCodeBlock = true;
|
|
s_codeStart = renderer->getCursorPos().y;
|
|
renderer->pushFont(g_mdMonoFont);
|
|
renderer->pushIndent(g_codeBlockIndent);
|
|
renderer->pushWrapOffset(g_codeBlockIndent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
log::warn("Unhandled block enter type {}", type);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int leaveBlock(MD_BLOCKTYPE type, void* detail, void* mdtextarea) {
|
|
auto textarea = static_cast<MDTextArea*>(mdtextarea);
|
|
auto renderer = textarea->m_renderer;
|
|
switch (type) {
|
|
case MD_BLOCKTYPE::MD_BLOCK_DOC:
|
|
{
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_H:
|
|
{
|
|
auto hdetail = static_cast<MD_BLOCK_H_DETAIL*>(detail);
|
|
renderer->breakLine();
|
|
if (hdetail->level == 1) {
|
|
renderer->breakLine(g_paragraphPadding / 2);
|
|
renderer->renderNode(BreakLine::create(textarea->m_size.width));
|
|
}
|
|
renderer->breakLine(g_paragraphPadding);
|
|
renderer->popScale();
|
|
renderer->popStyleFlags();
|
|
// switch (hdetail->level) {
|
|
// case 3: renderer->popCaps(); break;
|
|
// }
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_P:
|
|
{
|
|
renderer->breakLine();
|
|
renderer->breakLine(g_paragraphPadding);
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_OL:
|
|
case MD_BLOCKTYPE::MD_BLOCK_UL:
|
|
{
|
|
renderer->popIndent();
|
|
renderer->breakLine();
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_CODE:
|
|
{
|
|
auto codeEnd = renderer->getCursorPos().y;
|
|
|
|
auto pad = g_codeBlockIndent / 1.5f;
|
|
|
|
CCSize size { textarea->m_size.width - renderer->getCurrentIndent() -
|
|
renderer->getCurrentWrapOffset() + pad * 2,
|
|
s_codeStart - codeEnd + pad * 2 };
|
|
|
|
auto bg =
|
|
CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
|
bg->setScale(.25f);
|
|
bg->setColor({ 0, 0, 0 });
|
|
bg->setOpacity(75);
|
|
bg->setContentSize(size * 4);
|
|
bg->setPosition(
|
|
size.width / 2 + renderer->getCurrentIndent() - pad,
|
|
// mmm i love magic numbers
|
|
// the -2.f is to offset the the box
|
|
// to fit the Ubuntu font very neatly.
|
|
// idk if it works the same for other
|
|
// fonts
|
|
s_codeStart - 2.f + pad - size.height / 2
|
|
);
|
|
bg->setAnchorPoint({ .5f, .5f });
|
|
bg->setZOrder(-1);
|
|
textarea->m_content->addChild(bg);
|
|
|
|
renderer->popWrapOffset();
|
|
renderer->popIndent();
|
|
renderer->popFont();
|
|
|
|
renderer->breakLine();
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_LI:
|
|
{
|
|
renderer->breakLine();
|
|
}
|
|
break;
|
|
|
|
case MD_BLOCKTYPE::MD_BLOCK_HR:
|
|
{
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
log::warn("Unhandled block leave type {}", type);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int enterSpan(MD_SPANTYPE type, void* detail, void* mdtextarea) {
|
|
auto renderer = static_cast<MDTextArea*>(mdtextarea)->m_renderer;
|
|
switch (type) {
|
|
case MD_SPANTYPE::MD_SPAN_STRONG:
|
|
{
|
|
renderer->pushStyleFlags(TextStyleBold);
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_EM:
|
|
{
|
|
renderer->pushStyleFlags(TextStyleItalic);
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_DEL:
|
|
{
|
|
renderer->pushDecoFlags(TextDecorationStrikethrough);
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_U:
|
|
{
|
|
renderer->pushDecoFlags(TextDecorationUnderline);
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_IMG:
|
|
{
|
|
auto adetail = static_cast<MD_SPAN_IMG_DETAIL*>(detail);
|
|
s_lastImage = std::string(adetail->src.text, adetail->src.size);
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_A:
|
|
{
|
|
auto adetail = static_cast<MD_SPAN_A_DETAIL*>(detail);
|
|
s_lastLink = std::string(adetail->href.text, adetail->href.size);
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_CODE:
|
|
{
|
|
s_isCodeBlock = false;
|
|
renderer->pushFont(g_mdMonoFont);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
log::warn("Unhandled span enter type {}", type);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int leaveSpan(MD_SPANTYPE type, void* detail, void* mdtextarea) {
|
|
auto renderer = static_cast<MDTextArea*>(mdtextarea)->m_renderer;
|
|
switch (type) {
|
|
case MD_SPANTYPE::MD_SPAN_STRONG:
|
|
{
|
|
renderer->popStyleFlags();
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_EM:
|
|
{
|
|
renderer->popStyleFlags();
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_DEL:
|
|
{
|
|
renderer->popDecoFlags();
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_U:
|
|
{
|
|
renderer->popDecoFlags();
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_A:
|
|
{
|
|
s_lastLink = "";
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_IMG:
|
|
{
|
|
s_lastImage = "";
|
|
}
|
|
break;
|
|
|
|
case MD_SPANTYPE::MD_SPAN_CODE:
|
|
{
|
|
renderer->popFont();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
log::warn("Unhandled span leave type {}", type);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
std::string MDParser::s_lastLink = "";
|
|
std::string MDParser::s_lastImage = "";
|
|
bool MDParser::s_isOrderedList = false;
|
|
size_t MDParser::s_orderedListNum = 0;
|
|
bool MDParser::s_isCodeBlock = false;
|
|
float MDParser::s_codeStart = 0;
|
|
decltype(MDParser::s_codeSpans) MDParser::s_codeSpans = {};
|
|
|
|
void MDTextArea::updateLabel() {
|
|
m_renderer->begin(m_content, CCPointZero, m_size);
|
|
|
|
m_renderer->pushFont(g_mdFont);
|
|
m_renderer->pushScale(.5f);
|
|
m_renderer->pushVerticalAlign(TextAlignment::End);
|
|
m_renderer->pushHorizontalAlign(TextAlignment::Begin);
|
|
|
|
MD_PARSER parser;
|
|
|
|
parser.abi_version = 0;
|
|
parser.flags = MD_FLAG_UNDERLINE | MD_FLAG_STRIKETHROUGH | MD_FLAG_PERMISSIVEURLAUTOLINKS |
|
|
MD_FLAG_PERMISSIVEWWWAUTOLINKS;
|
|
|
|
parser.text = &MDParser::parseText;
|
|
parser.enter_block = &MDParser::enterBlock;
|
|
parser.leave_block = &MDParser::leaveBlock;
|
|
parser.enter_span = &MDParser::enterSpan;
|
|
parser.leave_span = &MDParser::leaveSpan;
|
|
parser.debug_log = nullptr;
|
|
parser.syntax = nullptr;
|
|
|
|
MDParser::s_codeSpans = {};
|
|
|
|
if (md_parse(m_text.c_str(), m_text.size(), &parser, this)) {
|
|
m_renderer->renderString("Error parsing Markdown");
|
|
}
|
|
|
|
for (auto& render : MDParser::s_codeSpans) {
|
|
auto bg = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
|
bg->setScale(.125f);
|
|
bg->setColor({ 0, 0, 0 });
|
|
bg->setOpacity(75);
|
|
bg->setContentSize(render.m_node->getScaledContentSize() * 8 + CCSize { 20.f, .0f });
|
|
bg->setPosition(
|
|
render.m_node->getPositionX() - 2.5f * (.5f - render.m_node->getAnchorPoint().x),
|
|
render.m_node->getPositionY() - .5f
|
|
);
|
|
bg->setAnchorPoint(render.m_node->getAnchorPoint());
|
|
bg->setZOrder(-1);
|
|
m_content->addChild(bg);
|
|
// i know what you're thinking.
|
|
// my brother in christ, what the hell is this?
|
|
// where did this magical + 1.5f come from?
|
|
// the reason is that if you remove them, code
|
|
// spans are slightly offset and it triggers my
|
|
// OCD.
|
|
render.m_node->setPositionY(render.m_node->getPositionY() + 1.5f);
|
|
}
|
|
|
|
m_renderer->end();
|
|
|
|
m_scrollLayer->m_contentLayer->setContentSize(m_content->getContentSize());
|
|
m_scrollLayer->moveToTop();
|
|
}
|
|
|
|
CCScrollLayerExt* MDTextArea::getScrollLayer() const {
|
|
return m_scrollLayer;
|
|
}
|
|
|
|
void MDTextArea::setString(char const* text) {
|
|
m_text = text;
|
|
this->updateLabel();
|
|
}
|
|
|
|
char const* MDTextArea::getString() {
|
|
return m_text.c_str();
|
|
}
|
|
|
|
MDTextArea* MDTextArea::create(std::string const& str, CCSize const& size) {
|
|
auto ret = new MDTextArea;
|
|
if (ret && ret->init(str, size)) {
|
|
ret->autorelease();
|
|
return ret;
|
|
}
|
|
CC_SAFE_DELETE(ret);
|
|
return nullptr;
|
|
}
|