add markdown dialect (WIP) & add strikethrough and underline font styles
This commit is contained in:
parent
605d7e7897
commit
80e809f97f
@ -22,6 +22,7 @@
|
||||
multiline='true'
|
||||
size-func="gui.get_viewport()[1],40"
|
||||
gravity="bottom-left"
|
||||
markdown="true"
|
||||
></textbox>
|
||||
</container>
|
||||
<container id="editorContainer" pos="0,60" color="#00000080"
|
||||
@ -54,6 +55,7 @@
|
||||
<textbox id='prompt'
|
||||
consumer='submit'
|
||||
margin='0'
|
||||
markdown="true"
|
||||
gravity='bottom-left'
|
||||
size-func="gui.get_viewport()[1],40"
|
||||
onup="on_history_up()"
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "commons.hpp"
|
||||
|
||||
using namespace lua;
|
||||
using namespace devtools;
|
||||
|
||||
static std::set<std::string_view> keywords {
|
||||
"and", "break", "do", "else", "elseif", "end", "false", "for", "function",
|
||||
|
||||
@ -3,33 +3,12 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "devtools/syntax.hpp"
|
||||
|
||||
namespace lua {
|
||||
struct Location {
|
||||
int pos;
|
||||
int lineStart;
|
||||
int line;
|
||||
};
|
||||
|
||||
enum class TokenTag {
|
||||
KEYWORD, NAME, INTEGER, NUMBER, OPEN_BRACKET, CLOSE_BRACKET, STRING,
|
||||
OPERATOR, COMMA, SEMICOLON, UNEXPECTED, COMMENT
|
||||
};
|
||||
|
||||
struct Token {
|
||||
TokenTag tag;
|
||||
std::string text;
|
||||
Location start;
|
||||
Location end;
|
||||
|
||||
Token(TokenTag tag, std::string text, Location start, Location end)
|
||||
: tag(tag),
|
||||
text(std::move(text)),
|
||||
start(std::move(start)),
|
||||
end(std::move(end)) {
|
||||
}
|
||||
};
|
||||
|
||||
bool is_lua_keyword(std::string_view view);
|
||||
|
||||
std::vector<Token> tokenize(std::string_view file, std::string_view source);
|
||||
std::vector<devtools::Token> tokenize(
|
||||
std::string_view file, std::string_view source
|
||||
);
|
||||
}
|
||||
|
||||
30
src/devtools/syntax.hpp
Normal file
30
src/devtools/syntax.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace devtools {
|
||||
struct Location {
|
||||
int pos;
|
||||
int lineStart;
|
||||
int line;
|
||||
};
|
||||
|
||||
enum class TokenTag {
|
||||
KEYWORD, NAME, INTEGER, NUMBER, OPEN_BRACKET, CLOSE_BRACKET, STRING,
|
||||
OPERATOR, COMMA, SEMICOLON, UNEXPECTED, COMMENT
|
||||
};
|
||||
|
||||
struct Token {
|
||||
TokenTag tag;
|
||||
std::string text;
|
||||
Location start;
|
||||
Location end;
|
||||
|
||||
Token(TokenTag tag, std::string text, Location start, Location end)
|
||||
: tag(tag),
|
||||
text(std::move(text)),
|
||||
start(std::move(start)),
|
||||
end(std::move(end)) {
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -7,28 +7,28 @@
|
||||
using namespace devtools;
|
||||
|
||||
static std::unique_ptr<FontStylesScheme> build_styles(
|
||||
const std::vector<lua::Token>& tokens
|
||||
const std::vector<devtools::Token>& tokens
|
||||
) {
|
||||
using devtools::TokenTag;
|
||||
FontStylesScheme styles {
|
||||
{
|
||||
{false, false, glm::vec4(0.8f, 0.8f, 0.8f, 1)}, // default
|
||||
{true, false, glm::vec4(0.9, 0.6f, 0.4f, 1)}, // keyword
|
||||
{false, false, glm::vec4(0.4, 0.8f, 0.5f, 1)}, // string
|
||||
{false, false, glm::vec4(0.3, 0.3f, 0.3f, 1)}, // comment
|
||||
{false, false, glm::vec4(0.4, 0.45f, 0.5f, 1)}, // self
|
||||
{true, false, glm::vec4(1.0f, 0.2f, 0.1f, 1)}, // unexpected
|
||||
{false, false, false, false, glm::vec4(0.8f, 0.8f, 0.8f, 1)}, // default
|
||||
{true, false, false, false, glm::vec4(0.9, 0.6f, 0.4f, 1)}, // keyword
|
||||
{false, false, false, false, glm::vec4(0.4, 0.8f, 0.5f, 1)}, // string
|
||||
{false, false, false, false, glm::vec4(0.3, 0.3f, 0.3f, 1)}, // comment
|
||||
{true, false, false, false, glm::vec4(1.0f, 0.2f, 0.1f, 1)}, // unexpected
|
||||
},
|
||||
{}
|
||||
};
|
||||
size_t offset = 0;
|
||||
for (int i = 0; i < tokens.size(); i++) {
|
||||
const auto& token = tokens.at(i);
|
||||
if (token.tag != lua::TokenTag::KEYWORD &&
|
||||
token.tag != lua::TokenTag::STRING &&
|
||||
token.tag != lua::TokenTag::INTEGER &&
|
||||
token.tag != lua::TokenTag::NUMBER &&
|
||||
token.tag != lua::TokenTag::COMMENT &&
|
||||
token.tag != lua::TokenTag::UNEXPECTED) {
|
||||
if (token.tag != TokenTag::KEYWORD &&
|
||||
token.tag != TokenTag::STRING &&
|
||||
token.tag != TokenTag::INTEGER &&
|
||||
token.tag != TokenTag::NUMBER &&
|
||||
token.tag != TokenTag::COMMENT &&
|
||||
token.tag != TokenTag::UNEXPECTED) {
|
||||
continue;
|
||||
}
|
||||
if (token.start.pos > offset) {
|
||||
@ -38,12 +38,12 @@ static std::unique_ptr<FontStylesScheme> build_styles(
|
||||
offset = token.end.pos;
|
||||
int styleIndex;
|
||||
switch (token.tag) {
|
||||
case lua::TokenTag::KEYWORD: styleIndex = 1; break;
|
||||
case lua::TokenTag::STRING:
|
||||
case lua::TokenTag::INTEGER:
|
||||
case lua::TokenTag::NUMBER: styleIndex = 2; break;
|
||||
case lua::TokenTag::COMMENT: styleIndex = 3; break;
|
||||
case lua::TokenTag::UNEXPECTED: styleIndex = 5; break;
|
||||
case TokenTag::KEYWORD: styleIndex = SyntaxStyles::KEYWORD; break;
|
||||
case TokenTag::STRING:
|
||||
case TokenTag::INTEGER:
|
||||
case TokenTag::NUMBER: styleIndex = SyntaxStyles::LITERAL; break;
|
||||
case TokenTag::COMMENT: styleIndex = SyntaxStyles::COMMENT; break;
|
||||
case TokenTag::UNEXPECTED: styleIndex = SyntaxStyles::ERROR; break;
|
||||
default:
|
||||
styleIndex = 0;
|
||||
break;
|
||||
|
||||
@ -6,6 +6,10 @@
|
||||
struct FontStylesScheme;
|
||||
|
||||
namespace devtools {
|
||||
enum SyntaxStyles {
|
||||
DEFAULT, KEYWORD, LITERAL, COMMENT, ERROR
|
||||
};
|
||||
|
||||
std::unique_ptr<FontStylesScheme> syntax_highlight(
|
||||
const std::string& lang, std::string_view source
|
||||
);
|
||||
|
||||
@ -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(),
|
||||
|
||||
109
src/graphics/ui/markdown.cpp
Normal file
109
src/graphics/ui/markdown.cpp
Normal file
@ -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);
|
||||
}
|
||||
22
src/graphics/ui/markdown.hpp
Normal file
22
src/graphics/ui/markdown.hpp
Normal file
@ -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);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user