add markdown dialect (WIP) & add strikethrough and underline font styles
This commit is contained in:
@@ -101,7 +101,7 @@ static inline void draw_text(
|
||||
const glm::vec3& pos,
|
||||
const glm::vec3& right,
|
||||
const glm::vec3& up,
|
||||
float glyphInterval,
|
||||
float interval,
|
||||
const FontStylesScheme* styles,
|
||||
size_t styleMapOffset
|
||||
) {
|
||||
@@ -115,6 +115,7 @@ static inline void draw_text(
|
||||
uint next = MAX_CODEPAGES;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
bool hasLines = false;
|
||||
|
||||
do {
|
||||
for (size_t i = 0; i < text.length(); i++) {
|
||||
@@ -123,6 +124,9 @@ static inline void draw_text(
|
||||
std::min(styles->map.size() - 1, i + styleMapOffset)
|
||||
);
|
||||
const FontStyle& style = styles->palette.at(styleIndex);
|
||||
hasLines |= style.strikethrough;
|
||||
hasLines |= style.underline;
|
||||
|
||||
if (!font.isPrintableChar(c)) {
|
||||
x++;
|
||||
continue;
|
||||
@@ -131,14 +135,7 @@ static inline void draw_text(
|
||||
if (charpage == page){
|
||||
batch.texture(font.getPage(charpage));
|
||||
draw_glyph(
|
||||
batch,
|
||||
pos,
|
||||
glm::vec2(x, y),
|
||||
c,
|
||||
right,
|
||||
up,
|
||||
glyphInterval,
|
||||
style
|
||||
batch, pos, glm::vec2(x, y), c, right, up, interval, style
|
||||
);
|
||||
}
|
||||
else if (charpage > page && charpage < next){
|
||||
@@ -150,6 +147,31 @@ static inline void draw_text(
|
||||
next = MAX_CODEPAGES;
|
||||
x = 0;
|
||||
} while (page < MAX_CODEPAGES);
|
||||
|
||||
if (!hasLines) {
|
||||
return;
|
||||
}
|
||||
batch.texture(font.getPage(0));
|
||||
for (size_t i = 0; i < text.length(); i++) {
|
||||
uint c = text[i];
|
||||
size_t styleIndex = styles->map.at(
|
||||
std::min(styles->map.size() - 1, i + styleMapOffset)
|
||||
);
|
||||
const FontStyle& style = styles->palette.at(styleIndex);
|
||||
FontStyle lineStyle = style;
|
||||
lineStyle.bold = true;
|
||||
if (style.strikethrough) {
|
||||
draw_glyph(
|
||||
batch, pos, glm::vec2(x, y), '-', right, up, interval, lineStyle
|
||||
);
|
||||
}
|
||||
if (style.underline) {
|
||||
draw_glyph(
|
||||
batch, pos, glm::vec2(x, y), '_', right, up, interval, lineStyle
|
||||
);
|
||||
}
|
||||
x++;
|
||||
}
|
||||
}
|
||||
|
||||
const Texture* Font::getPage(int charpage) const {
|
||||
|
||||
@@ -14,7 +14,25 @@ class Camera;
|
||||
struct FontStyle {
|
||||
bool bold = false;
|
||||
bool italic = false;
|
||||
bool strikethrough = false;
|
||||
bool underline = false;
|
||||
glm::vec4 color {1, 1, 1, 1};
|
||||
|
||||
FontStyle() = default;
|
||||
|
||||
FontStyle(
|
||||
bool bold,
|
||||
bool italic,
|
||||
bool strikethrough,
|
||||
bool underline,
|
||||
glm::vec4 color
|
||||
)
|
||||
: bold(bold),
|
||||
italic(italic),
|
||||
strikethrough(strikethrough),
|
||||
underline(underline),
|
||||
color(std::move(color)) {
|
||||
}
|
||||
};
|
||||
|
||||
struct FontStylesScheme {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "graphics/core/Font.hpp"
|
||||
#include "assets/Assets.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "../markdown.hpp"
|
||||
|
||||
using namespace gui;
|
||||
|
||||
@@ -80,11 +81,16 @@ glm::vec2 Label::calcSize() {
|
||||
);
|
||||
}
|
||||
|
||||
void Label::setText(const std::wstring& text) {
|
||||
void Label::setText(std::wstring text) {
|
||||
if (isMarkdown()) {
|
||||
auto [processedText, styles] = markdown::process(text, true);
|
||||
text = std::move(processedText);
|
||||
setStyles(std::move(styles));
|
||||
}
|
||||
if (text == this->text && !cache.resetFlag) {
|
||||
return;
|
||||
}
|
||||
this->text = text;
|
||||
this->text = std::move(text);
|
||||
cache.update(this->text, multiline, textWrap);
|
||||
|
||||
if (cache.font && autoresize) {
|
||||
@@ -242,6 +248,15 @@ bool Label::isTextWrapping() const {
|
||||
return textWrap;
|
||||
}
|
||||
|
||||
void Label::setMarkdown(bool flag) {
|
||||
markdown = flag;
|
||||
setText(text);
|
||||
}
|
||||
|
||||
bool Label::isMarkdown() const {
|
||||
return markdown;
|
||||
}
|
||||
|
||||
void Label::setStyles(std::unique_ptr<FontStylesScheme> styles) {
|
||||
this->styles = std::move(styles);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace gui {
|
||||
/// @brief Auto resize label to fit text
|
||||
bool autoresize = false;
|
||||
|
||||
/// @brief Enable text markdown
|
||||
bool markdown = false;
|
||||
|
||||
std::unique_ptr<FontStylesScheme> styles;
|
||||
public:
|
||||
Label(const std::string& text, std::string fontName="normal");
|
||||
@@ -60,7 +63,7 @@ namespace gui {
|
||||
|
||||
virtual ~Label();
|
||||
|
||||
virtual void setText(const std::wstring& text);
|
||||
virtual void setText(std::wstring text);
|
||||
const std::wstring& getText() const;
|
||||
|
||||
virtual void setFontName(std::string name);
|
||||
@@ -113,6 +116,9 @@ namespace gui {
|
||||
virtual void setTextWrapping(bool flag);
|
||||
virtual bool isTextWrapping() const;
|
||||
|
||||
virtual void setMarkdown(bool flag);
|
||||
virtual bool isMarkdown() const;
|
||||
|
||||
virtual void setStyles(std::unique_ptr<FontStylesScheme> styles);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "util/stringutil.hpp"
|
||||
#include "window/Events.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "../markdown.hpp"
|
||||
|
||||
using namespace gui;
|
||||
|
||||
@@ -191,7 +192,18 @@ void TextBox::drawBackground(const DrawContext& pctx, const Assets&) {
|
||||
|
||||
void TextBox::refreshLabel() {
|
||||
label->setColor(textColor * glm::vec4(input.empty() ? 0.5f : 1.0f));
|
||||
label->setText(input.empty() && !hint.empty() ? hint : getText());
|
||||
|
||||
const auto& displayText = input.empty() && !hint.empty() ? hint : getText();
|
||||
if (markdown) {
|
||||
auto [processedText, styles] = markdown::process(displayText, !focused);
|
||||
label->setText(std::move(processedText));
|
||||
label->setStyles(std::move(styles));
|
||||
} else {
|
||||
label->setText(displayText);
|
||||
if (syntax.empty()) {
|
||||
label->setStyles(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (showLineNumbers) {
|
||||
if (lineNumbersLabel->getLinesNumber() != label->getLinesNumber()) {
|
||||
@@ -846,3 +858,11 @@ void TextBox::setSyntax(const std::string& lang) {
|
||||
refreshSyntax();
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::setMarkdown(bool flag) {
|
||||
markdown = flag;
|
||||
}
|
||||
|
||||
bool TextBox::isMarkdown() const {
|
||||
return markdown;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace gui {
|
||||
bool editable = true;
|
||||
bool autoresize = false;
|
||||
bool showLineNumbers = false;
|
||||
bool markdown = false;
|
||||
|
||||
std::string syntax;
|
||||
|
||||
@@ -227,5 +228,8 @@ namespace gui {
|
||||
virtual void setOnDownPressed(const runnable &callback);
|
||||
|
||||
virtual void setSyntax(const std::string& lang);
|
||||
|
||||
virtual void setMarkdown(bool flag);
|
||||
virtual bool isMarkdown() const;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -278,6 +278,9 @@ static std::shared_ptr<UINode> readLabel(
|
||||
if (element.has("text-wrap")) {
|
||||
label->setTextWrapping(element.attr("text-wrap").asBool());
|
||||
}
|
||||
if (element.has("markdown")) {
|
||||
label->setMarkdown(element.attr("markdown").asBool());
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
@@ -381,6 +384,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
|
||||
if (element.has("line-numbers")) {
|
||||
textbox->setShowLineNumbers(element.attr("line-numbers").asBool());
|
||||
}
|
||||
if (element.has("markdown")) {
|
||||
textbox->setMarkdown(element.attr("markdown").asBool());
|
||||
}
|
||||
if (element.has("consumer")) {
|
||||
textbox->setTextConsumer(scripting::create_wstring_consumer(
|
||||
reader.getEnvironment(),
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "markdown.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "graphics/core/Font.hpp"
|
||||
|
||||
using namespace markdown;
|
||||
|
||||
template <typename CharT>
|
||||
static inline void emit(
|
||||
CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss
|
||||
) {
|
||||
ss << c;
|
||||
styles.map.emplace_back(styles.palette.size()-1);
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
static inline void emit_md(
|
||||
CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss
|
||||
) {
|
||||
ss << c;
|
||||
styles.map.emplace_back(0);
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
static inline void restyle(
|
||||
CharT c,
|
||||
FontStyle& style,
|
||||
FontStylesScheme& styles,
|
||||
std::basic_stringstream<CharT>& ss,
|
||||
int& pos,
|
||||
bool eraseMarkdown
|
||||
) {
|
||||
styles.palette.push_back(style);
|
||||
if (!eraseMarkdown) {
|
||||
emit_md(c, styles, ss);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
Result<CharT> process_markdown(
|
||||
std::basic_string_view<CharT> source, bool eraseMarkdown
|
||||
) {
|
||||
std::basic_stringstream<CharT> ss;
|
||||
FontStylesScheme styles {
|
||||
// markdown default
|
||||
{{false, false, false, false, glm::vec4(1,1,1,0.5f)}, {}},
|
||||
{}
|
||||
};
|
||||
FontStyle style;
|
||||
int pos = 0;
|
||||
while (pos < source.size()) {
|
||||
CharT first = source[pos];
|
||||
if (first == '\\') {
|
||||
if (pos + 1 < source.size()) {
|
||||
CharT second = source[++pos];
|
||||
switch (second) {
|
||||
case '*':
|
||||
case '_':
|
||||
case '~':
|
||||
if (!eraseMarkdown) {
|
||||
emit_md(first, styles, ss);
|
||||
}
|
||||
emit(second, styles, ss);
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (first == '*') {
|
||||
if (pos + 1 < source.size() && source[pos+1] == '*') {
|
||||
pos++;
|
||||
if (!eraseMarkdown)
|
||||
emit_md(first, styles, ss);
|
||||
style.bold = !style.bold;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
}
|
||||
style.italic = !style.italic;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
} else if (first == '_' && pos + 1 < source.size() && source[pos+1] == '_') {
|
||||
pos++;
|
||||
if (!eraseMarkdown)
|
||||
emit_md(first, styles, ss);
|
||||
style.underline = !style.underline;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
} else if (first == '~' && pos + 1 < source.size() && source[pos+1] == '~') {
|
||||
pos++;
|
||||
if (!eraseMarkdown)
|
||||
emit_md(first, styles, ss);
|
||||
style.strikethrough = !style.strikethrough;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
}
|
||||
emit(first, styles, ss);
|
||||
pos++;
|
||||
}
|
||||
return {ss.str(), std::make_unique<FontStylesScheme>(std::move(styles))};
|
||||
}
|
||||
|
||||
Result<char> markdown::process(std::string_view source, bool eraseMarkdown) {
|
||||
return process_markdown(source, eraseMarkdown);
|
||||
}
|
||||
|
||||
Result<wchar_t> markdown::process(std::wstring_view source, bool eraseMarkdown) {
|
||||
return process_markdown(source, eraseMarkdown);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
struct FontStylesScheme;
|
||||
|
||||
// VoxelCore Markdown dialect
|
||||
|
||||
namespace markdown {
|
||||
|
||||
template <typename CharT>
|
||||
struct Result {
|
||||
/// @brief Text with erased markdown
|
||||
std::basic_string<CharT> text;
|
||||
/// @brief Text styles scheme
|
||||
std::unique_ptr<FontStylesScheme> styles;
|
||||
};
|
||||
|
||||
Result<char> process(std::string_view source, bool eraseMarkdown);
|
||||
Result<wchar_t> process(std::wstring_view source, bool eraseMarkdown);
|
||||
}
|
||||
Reference in New Issue
Block a user