diff --git a/doc/en/main-page.md b/doc/en/main-page.md
index 67db8c10..0daa4729 100644
--- a/doc/en/main-page.md
+++ b/doc/en/main-page.md
@@ -14,9 +14,10 @@ Documentation for in-development version 0.26.
- [Content-packs](content-packs.md)
- [Engine usage recommendations](engine-use-recommendations.md)
- [Item properties](item-properties.md)
+- [Particles](particles.md)
- [Resources (resources.json)](resources.md)
- [Rigging](rigging.md)
- [Scripting](scripting.md)
+- [Text styles](text-styles.md)
- [World generator engine](world-generator.md)
- [XML UI building](xml-ui-layouts.md)
-- [Particles](particles.md)
diff --git a/doc/en/scripting/builtins/libgui.md b/doc/en/scripting/builtins/libgui.md
index 5ec2490f..3132bce3 100644
--- a/doc/en/scripting/builtins/libgui.md
+++ b/doc/en/scripting/builtins/libgui.md
@@ -39,3 +39,25 @@ gui.get_locales_info() -> table of tables {
```
Returns information about all loaded locales (res/texts/\*).
+
+```lua
+gui.clear_markup(
+ -- markup language ("md" - Markdown)
+ language: str,
+ -- text with markup
+ text: str
+) -> str
+```
+
+Removes markup from text.
+
+```lua
+gui.escape_markup(
+ -- markup language ("md" - Markdown)
+ language: str,
+ -- text with markup
+ text: str
+) -> str
+```
+
+Escapes markup in text.
diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md
index 4f132462..c990a197 100644
--- a/doc/en/scripting/ui.md
+++ b/doc/en/scripting/ui.md
@@ -32,7 +32,7 @@ document["worlds-panel"]:clear()
Properties that apply to all elements:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| ------------- | ------ | ---- | ----- | ------------------------------------------- |
| id | string | yes | *no* | element id |
| pos | vec2 | yes | yes | element position inside a container |
@@ -60,17 +60,17 @@ Common element methods:
Common methods for containers (elements: container, panel, button, pagebox):
-| Method | Description |
-| ------------------------------- | ------------------------------------------------------------------ |
-| clear() | clears content |
-| add(xml) | adds an element, creating it using xml code. Example: `container:add("")` |
-| setInterval(interval, callback) | assigns a function to be executed repeatedly at an interval specified in milliseconds |
+| Method | Description |
+| ------------------------------- | -------------------------------------------------------------------------------------------- |
+| clear() | clears content |
+| add(xml) | adds an element, creating it using xml code. Example: `container:add("")` |
+| setInterval(interval, callback) | assigns a function to be executed repeatedly at an interval specified in milliseconds |
## Textbox
Properties:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| ----------- | ------ | ---- | ----- | ------------------------------------------------------------------------------------ |
| text | string | yes | yes | entered text or placeholder |
| placeholder | string | yes | yes | placeholder (used if nothing has been entered) |
@@ -82,6 +82,8 @@ Properties:
| textWrap | bool | yes | yes | automatic text wrapping (only with multiline: "true") |
| valid | bool | yes | no | is the entered text correct |
| textColor | vec4 | yes | yes | text color |
+| syntax | string | yes | yes | syntax highlighting ("lua" - Lua) |
+| markup | string | yes | yes | text markup language ("md" - Markdown) |
Methods:
@@ -95,7 +97,7 @@ Methods:
Properties:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| ---------- | ----- | ---- | ----- | --------------------- |
| value | float | yes | yes | current value |
| min | float | yes | yes | minimum value |
@@ -108,7 +110,7 @@ Properties:
Properties:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| ----- | ------ | ---- | ----- | ------------ |
| page | string | yes | yes | current page |
@@ -123,7 +125,7 @@ Methods:
Properties:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| ------- | ---- | ---- | ----- | ----------- |
| checked | bool | yes | yes | mark status |
@@ -131,7 +133,7 @@ Properties:
Properties:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| ----- | ------ | ---- | ----- | ------------ |
| text | string | yes | yes | button text |
@@ -139,15 +141,16 @@ Properties:
Properties:
-| Title | Type | Read | Write | Description |
-| ----- | ------ | ---- | ----- | ----------- |
-| text | string | yes | yes | label text |
+| Name | Type | Read | Write | Description |
+| ------ | ------ | ---- | ----- | -------------------------------------- |
+| text | string | yes | yes | label text |
+| markup | string | yes | yes | text markup language ("md" - Markdown) |
## Image
Properties:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| ----- | ------ | ---- | ----- | ------------ |
| src | string | yes | yes | texture name |
@@ -155,6 +158,6 @@ Properties:
Properties:
-| Title | Type | Read | Write | Description |
+| Name | Type | Read | Write | Description |
| --------- | ---- | ---- | ----- | ------------------------------------------------- |
| inventory | int | yes | yes | id of the inventory to which the element is bound |
diff --git a/doc/en/text-styles.md b/doc/en/text-styles.md
new file mode 100644
index 00000000..e85437e1
--- /dev/null
+++ b/doc/en/text-styles.md
@@ -0,0 +1,21 @@
+# Text styles
+
+A proprietary Markdown dialect is used to mark up text styles.
+Formatting works on UI elements: label and textbox, if `markup="md"` is explicitly specified.
+
+## Styles
+
+| Style | Example | Output |
+| ------------- | ------------------------ | -------------------------- |
+| Bold | `**Bold font**` | **Bold font** |
+| Italic | `*Text in italics*` | *Text in italics* |
+| Underline | `__Underlined text__` | Underlined text |
+| Strikethrough | `~~Strikethrough text~~` | ~~Strikethrough text~~ |
+
+Styles can be combined. Example:
+```md
+***__Message__*** using *~~combed~~ combined* styles__~~.~~__
+```
+Output:
+
+***Message*** using *~~combed~~ combined* styles~~.~~
diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md
index d77a93ed..e7008682 100644
--- a/doc/en/xml-ui-layouts.md
+++ b/doc/en/xml-ui-layouts.md
@@ -87,6 +87,7 @@ Inner text is a button text.
- `autoresize` - automatic change of element size (default - false). Does not affect font size.
- `multiline` - allows display of multiline text.
- `text-wrap` - allows automatic text wrapping (works only with multiline: "true").
+- `markup` - text markup language ("md" - Markdown).
## *image*
@@ -112,6 +113,8 @@ Inner text - initially entered text
- `validator` - lua function that checks text for correctness. Takes a string as input, returns true if the text is correct.
- `onup` - lua function called when the up arrow is pressed.
- `ondown` - lua function called when the down arrow is pressed.
+- `syntax` - syntax highlighting ("lua" - Lua).
+- `markup` - text markup language ("md" - Markdown).
## *trackbar*
diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md
index 17dbe6ba..10dff292 100644
--- a/doc/ru/main-page.md
+++ b/doc/ru/main-page.md
@@ -19,4 +19,5 @@
- [Свойства блоков](block-properties.md)
- [Свойства предметов](item-properties.md)
- [Скриптинг](scripting.md)
+- [Стили текста](text-styles.md)
- [Частицы](particles.md)
diff --git a/doc/ru/scripting/builtins/libgui.md b/doc/ru/scripting/builtins/libgui.md
index eb81cd5f..11dfde03 100644
--- a/doc/ru/scripting/builtins/libgui.md
+++ b/doc/ru/scripting/builtins/libgui.md
@@ -36,3 +36,25 @@ gui.get_locales_info() -> таблица таблиц где
```
Возвращает информацию о всех загруженных локалях (res/texts/\*).
+
+```lua
+gui.clear_markup(
+ -- язык разметки ("md" - Markdown)
+ language: str,
+ -- текст с разметкой
+ text: str
+) -> str
+```
+
+Удаляет разметку из текста.
+
+```lua
+gui.escape_markup(
+ -- язык разметки ("md" - Markdown)
+ language: str,
+ -- текст с разметкой
+ text: str
+) -> str
+```
+
+Экранирует разметку в тексте.
diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md
index 4d244210..07d93013 100644
--- a/doc/ru/scripting/ui.md
+++ b/doc/ru/scripting/ui.md
@@ -82,6 +82,8 @@ document["worlds-panel"]:clear()
| textWrap | bool | да | да | автоматический перенос текста (только при multiline: "true") |
| valid | bool | да | нет | является ли введенный текст корректным |
| textColor | vec4 | да | да | цвет текста |
+| syntax | string | да | да | подсветка синтаксиса ("lua" - Lua) |
+| markup | string | да | да | язык разметки текста ("md" - Markdown) |
Методы:
@@ -139,9 +141,10 @@ document["worlds-panel"]:clear()
Свойства:
-| Название | Тип | Чтение | Запись | Описание |
-| -------- | ------ | ------ | ------ | ----------- |
-| text | string | да | да | текст метки |
+| Название | Тип | Чтение | Запись | Описание |
+| -------- | ------ | ------ | ------ | -------------------------------------- |
+| text | string | да | да | текст метки |
+| markup | string | да | да | язык разметки текста ("md" - Markdown) |
## Изображение (image)
diff --git a/doc/ru/text-styles.md b/doc/ru/text-styles.md
new file mode 100644
index 00000000..0a09abe3
--- /dev/null
+++ b/doc/ru/text-styles.md
@@ -0,0 +1,21 @@
+# Стили текста
+
+Для разметки стилей текста используется собственный диалект Markdown.
+Форматирование работает на UI элементах: label и textbox, если явно указано `markup="md"`.
+
+## Стили
+
+| Стиль | Пример | Вывод |
+| ------------ | ------------------------- | ----------------------------- |
+| Жирный | `**Жирный шрифт**` | **Жирный шрифт** |
+| Курсив | `*Текст курсивом*` | *Текст курсивом* |
+| Подчеркнутый | `__Подчеркнутый текст__` | Подчеркнутый текст |
+| Зачеркнутый | `~~Зачеркнутый текст~~` | ~~Зачеркнутый текст~~ |
+
+Стили могут объединяться. Пример:
+```md
+***__Сообщение__***, демонстрирующее *~~обедненные~~ объединенные* стили__~~.~~__
+```
+Вывод:
+
+***Сообщение***, демонстрирующее *~~обедненные~~ объединенные* стили~~.~~
diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md
index 5c71690f..1ad93c4f 100644
--- a/doc/ru/xml-ui-layouts.md
+++ b/doc/ru/xml-ui-layouts.md
@@ -89,6 +89,7 @@
- `autoresize` - автоматическое изменение размера элемента (по-умолчанию - false). Не влияет на размер шрифта.
- `multiline` - разрешает отображение многострочного текста.
- `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true")
+- `markup` - язык разметки текста ("md" - Markdown).
## Изображение - *image*
@@ -113,6 +114,8 @@
- `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен.
- `onup` - lua функция вызываемая при нажатии стрелки вверх.
- `ondown` - lua функция вызываемая при нажатии стрелки вниз.
+- `syntax` - подстветка синтаксиса ("lua" - Lua).
+- `markup` - язык разметки текста ("md" - Markdown).
## Ползунок - *trackbar*
diff --git a/res/layouts/console.xml b/res/layouts/console.xml
index c367545c..43e72dd8 100644
--- a/res/layouts/console.xml
+++ b/res/layouts/console.xml
@@ -22,6 +22,7 @@
multiline='true'
size-func="gui.get_viewport()[1],40"
gravity="bottom-left"
+ markup="md"
>
+
+#include "commons.hpp"
+
+using namespace lua;
+using namespace devtools;
+
+static std::set keywords {
+ "and", "break", "do", "else", "elseif", "end", "false", "for", "function",
+ "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true",
+ "until", "while"
+};
+
+bool lua::is_lua_keyword(std::string_view view) {
+ return keywords.find(view) != keywords.end();
+}
+
+inline bool is_lua_identifier_start(int c) {
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
+}
+
+inline bool is_lua_identifier_part(int c) {
+ return is_lua_identifier_start(c) || is_digit(c);
+}
+
+inline bool is_lua_operator_start(int c) {
+ return c == '=' || c == '~' || c == '+' || c == '-' || c == '/' || c == '*'
+ || c == '%' || c == '^' || c == '#' || c == '<' || c == '>' || c == ':'
+ || c == '.';
+}
+
+class Tokenizer : BasicParser {
+ std::vector tokens;
+public:
+ Tokenizer(std::string_view file, std::string_view source)
+ : BasicParser(file, source) {
+ }
+
+ std::string parseLuaName() {
+ char c = peek();
+ if (!is_identifier_start(c)) {
+ throw error("identifier expected");
+ }
+ int start = pos;
+ while (hasNext() && is_identifier_part(source[pos])) {
+ pos++;
+ }
+ return std::string(source.substr(start, pos - start));
+ }
+
+ inline Location currentLocation() const {
+ return Location {
+ static_cast(pos),
+ static_cast(linestart),
+ static_cast(line)};
+ }
+
+ void emitToken(
+ TokenTag tag, std::string name, Location start, bool standalone=false
+ ) {
+ tokens.emplace_back(
+ tag,
+ std::move(name),
+ std::move(start),
+ currentLocation()
+ );
+ if (standalone) skip(1);
+ }
+
+ /// @brief Get next operator token without checking operator for existing
+ std::string parseOperator() {
+ int start = pos;
+ char first = peek();
+ switch (first) {
+ case '#': case '+': case '/': case '*': case '^':
+ case '%':
+ skip(1);
+ return std::string({first});
+ case '-':
+ skip(1);
+ if (peekNoJump() == '-') {
+ skip(1);
+ return "--";
+ }
+ return std::string({first});
+ }
+ skip(1);
+ char second = peekNoJump();
+ if ((first == '=' && second == '=') || (first == '~' && second == '=') ||
+ (first == '<' && second == '=') || (first == '>' && second == '=')) {
+ skip(1);
+ return std::string(source.substr(start, pos - start));
+ }
+ if (first == '.' && second == '.') {
+ skip(1);
+ if (peekNoJump() == '.') {
+ skip(1);
+ }
+ }
+ return std::string(source.substr(start, pos - start));
+ }
+
+ std::vector tokenize() {
+ skipWhitespace();
+ while (hasNext()) {
+ skipWhitespace();
+ if (!hasNext()) {
+ continue;
+ }
+ char c = peek();
+ auto start = currentLocation();
+ if (is_lua_identifier_start(c)) {
+ auto name = parseLuaName();
+ emitToken(
+ is_lua_keyword(name) ? TokenTag::KEYWORD : TokenTag::NAME,
+ std::move(name),
+ start
+ );
+ continue;
+ } else if (is_digit(c)) {
+ dv::value value;
+ auto tag = TokenTag::UNEXPECTED;
+ try {
+ value = parseNumber(1);
+ tag = value.isInteger() ? TokenTag::INTEGER
+ : TokenTag::NUMBER;
+ } catch (const parsing_error& err) {}
+
+ auto literal = source.substr(start.pos, pos - start.pos);
+ emitToken(tag, std::string(literal), start);
+ continue;
+ }
+ switch (c) {
+ case '(': case '[': case '{':
+ if (isNext("[==[")) {
+ auto string = readUntil("]==]", true);
+ skip(4);
+ emitToken(TokenTag::COMMENT, std::string(string)+"]==]", start);
+ continue;
+ } else if (isNext("[[")) {
+ skip(2);
+ auto string = readUntil("]]", true);
+ skip(2);
+ emitToken(TokenTag::STRING, std::string(string), start);
+ continue;
+ }
+ emitToken(TokenTag::OPEN_BRACKET, std::string({c}), start, true);
+ continue;
+ case ')': case ']': case '}':
+ emitToken(TokenTag::CLOSE_BRACKET, std::string({c}), start, true);
+ continue;
+ case ',':
+ emitToken(TokenTag::COMMA, std::string({c}), start, true);
+ continue;
+ case ';':
+ emitToken(TokenTag::SEMICOLON, std::string({c}), start, true);
+ continue;
+ case '\'': case '"': {
+ skip(1);
+ auto string = parseString(c, false);
+ emitToken(TokenTag::STRING, std::move(string), start);
+ continue;
+ }
+ default: break;
+ }
+ if (is_lua_operator_start(c)) {
+ auto text = parseOperator();
+ if (text == "--") {
+ auto string = readUntilEOL();
+ emitToken(TokenTag::COMMENT, std::string(string), start);
+ skipLine();
+ continue;
+ }
+ emitToken(TokenTag::OPERATOR, std::move(text), start);
+ continue;
+ }
+ auto text = readUntilWhitespace();
+ emitToken(TokenTag::UNEXPECTED, std::string(text), start);
+ }
+ return std::move(tokens);
+ }
+};
+
+std::vector lua::tokenize(std::string_view file, std::string_view source) {
+ return Tokenizer(file, source).tokenize();
+}
diff --git a/src/coders/lua_parsing.hpp b/src/coders/lua_parsing.hpp
new file mode 100644
index 00000000..0054e4d0
--- /dev/null
+++ b/src/coders/lua_parsing.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+#include
+
+#include "devtools/syntax.hpp"
+
+namespace lua {
+ bool is_lua_keyword(std::string_view view);
+
+ std::vector tokenize(
+ std::string_view file, std::string_view source
+ );
+}
diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp
index faf84f15..b2cb8809 100644
--- a/src/coders/xml.cpp
+++ b/src/coders/xml.cpp
@@ -107,8 +107,8 @@ glm::vec4 Attribute::asColor() const {
Node::Node(std::string tag) : tag(std::move(tag)) {
}
-void Node::add(const xmlelement& element) {
- elements.push_back(element);
+void Node::add(std::unique_ptr element) {
+ elements.push_back(std::move(element));
}
void Node::set(const std::string& name, const std::string& text) {
@@ -119,7 +119,7 @@ const std::string& Node::getTag() const {
return tag;
}
-const xmlattribute& Node::attr(const std::string& name) const {
+const Attribute& Node::attr(const std::string& name) const {
auto found = attrs.find(name);
if (found == attrs.end()) {
throw std::runtime_error(
@@ -129,7 +129,7 @@ const xmlattribute& Node::attr(const std::string& name) const {
return found->second;
}
-xmlattribute Node::attr(const std::string& name, const std::string& def) const {
+Attribute Node::attr(const std::string& name, const std::string& def) const {
auto found = attrs.find(name);
if (found == attrs.end()) {
return Attribute(name, def);
@@ -142,19 +142,23 @@ bool Node::has(const std::string& name) const {
return found != attrs.end();
}
-xmlelement Node::sub(size_t index) {
- return elements.at(index);
+Node& Node::sub(size_t index) {
+ return *elements.at(index);
+}
+
+const Node& Node::sub(size_t index) const {
+ return *elements.at(index);
}
size_t Node::size() const {
return elements.size();
}
-const std::vector& Node::getElements() const {
+const std::vector>& Node::getElements() const {
return elements;
}
-const xmlelements_map& Node::getAttributes() const {
+const std::unordered_map& Node::getAttributes() const {
return attrs;
}
@@ -162,12 +166,12 @@ Document::Document(std::string version, std::string encoding)
: version(std::move(version)), encoding(std::move(encoding)) {
}
-void Document::setRoot(const xmlelement& element) {
- this->root = element;
+void Document::setRoot(std::unique_ptr element) {
+ root = std::move(element);
}
-xmlelement Document::getRoot() const {
- return root;
+const Node* Document::getRoot() const {
+ return root.get();
}
const std::string& Document::getVersion() const {
@@ -178,82 +182,6 @@ const std::string& Document::getEncoding() const {
return encoding;
}
-Parser::Parser(std::string_view filename, std::string_view source)
- : BasicParser(filename, source) {
-}
-
-xmlelement Parser::parseOpenTag() {
- std::string tag = parseXMLName();
- auto node = std::make_shared(tag);
-
- char c;
- while (true) {
- skipWhitespace();
- c = peek();
- if (c == '/' || c == '>' || c == '?') break;
- std::string attrname = parseXMLName();
- std::string attrtext = "";
- skipWhitespace();
- if (peek() == '=') {
- nextChar();
- skipWhitespace();
-
- char quote = peek();
- if (quote != '\'' && quote != '"') {
- throw error("string literal expected");
- }
- skip(1);
- attrtext = parseString(quote);
- }
- node->set(attrname, attrtext);
- }
- return node;
-}
-
-void Parser::parseDeclaration() {
- std::string version = "1.0";
- std::string encoding = "UTF-8";
- expect('<');
- if (peek() == '?') {
- nextChar();
- xmlelement node = parseOpenTag();
- expect("?>");
- if (node->getTag() != "xml") {
- throw error("invalid declaration");
- }
- version = node->attr("version", version).getText();
- encoding = node->attr("encoding", encoding).getText();
- if (encoding != "utf-8" && encoding != "UTF-8") {
- throw error("UTF-8 encoding is only supported");
- }
- } else {
- goBack();
- }
- document = std::make_shared(version, encoding);
-}
-
-void Parser::parseComment() {
- expect("!--");
- if (skipTo("-->")) {
- skip(3);
- } else {
- throw error("comment close missing");
- }
-}
-
-std::string Parser::parseText() {
- size_t start = pos;
- while (hasNext()) {
- char c = peek();
- if (c == '<') {
- break;
- }
- nextChar();
- }
- return Parser("[string]", std::string(source.substr(start, pos - start)))
- .parseString('\0', false);
-}
-
inline bool is_xml_identifier_start(char c) {
return is_identifier_start(c) || c == ':';
}
@@ -262,82 +190,166 @@ inline bool is_xml_identifier_part(char c) {
return is_identifier_part(c) || c == '-' || c == '.' || c == ':';
}
-std::string Parser::parseXMLName() {
- char c = peek();
- if (!is_xml_identifier_start(c)) {
- throw error("identifier expected");
- }
- int start = pos;
- while (hasNext() && is_xml_identifier_part(source[pos])) {
- pos++;
- }
- return std::string(source.substr(start, pos - start));
-}
+namespace {
+class Parser : BasicParser {
+ std::unique_ptr document;
-xmlelement Parser::parseElement() {
- // text element
- if (peek() != '<') {
- auto element = std::make_shared("#");
- auto text = parseText();
- util::replaceAll(text, """, "\"");
- util::replaceAll(text, "'", "'");
- util::replaceAll(text, "<", "<");
- util::replaceAll(text, ">", ">");
- util::replaceAll(text, "&", "&");
- element->set("#", text);
+ std::unique_ptr parseOpenTag() {
+ std::string tag = parseXMLName();
+ auto node = std::make_unique(tag);
+
+ char c;
+ while (true) {
+ skipWhitespace();
+ c = peek();
+ if (c == '/' || c == '>' || c == '?') break;
+ std::string attrname = parseXMLName();
+ std::string attrtext = "";
+ skipWhitespace();
+ if (peek() == '=') {
+ nextChar();
+ skipWhitespace();
+
+ char quote = peek();
+ if (quote != '\'' && quote != '"') {
+ throw error("string literal expected");
+ }
+ skip(1);
+ attrtext = parseString(quote);
+ }
+ node->set(attrname, attrtext);
+ }
+ return node;
+ }
+
+ std::unique_ptr parseElement() {
+ // text element
+ if (peek() != '<') {
+ auto element = std::make_unique("#");
+ auto text = parseText();
+ util::replaceAll(text, """, "\"");
+ util::replaceAll(text, "'", "'");
+ util::replaceAll(text, "<", "<");
+ util::replaceAll(text, ">", ">");
+ util::replaceAll(text, "&", "&");
+ element->set("#", text);
+ return element;
+ }
+ nextChar();
+
+ //
+ if (peek() == '!') {
+ if (isNext("!DOCTYPE ")) {
+ throw error("XML DTD is not supported yet");
+ }
+ parseComment();
+ return nullptr;
+ }
+
+ auto element = parseOpenTag();
+ char c = nextChar();
+
+ //
+ if (c == '/') {
+ expect('>');
+ }
+ // ...
+ else if (c == '>') {
+ skipWhitespace();
+ while (!isNext("")) {
+ auto sub = parseElement();
+ if (sub) {
+ element->add(std::move(sub));
+ }
+ skipWhitespace();
+ }
+ skip(2);
+ expect(element->getTag());
+ expect('>');
+ }
+ //
+ else {
+ throw error("invalid syntax");
+ }
return element;
}
- nextChar();
- //
- if (peek() == '!') {
- if (isNext("!DOCTYPE ")) {
- throw error("XML DTD is not supported yet");
- }
- parseComment();
- return nullptr;
- }
-
- auto element = parseOpenTag();
- char c = nextChar();
-
- //
- if (c == '/') {
- expect('>');
- }
- // ...
- else if (c == '>') {
- skipWhitespace();
- while (!isNext("")) {
- auto sub = parseElement();
- if (sub) {
- element->add(sub);
+ void parseDeclaration() {
+ std::string version = "1.0";
+ std::string encoding = "UTF-8";
+ expect('<');
+ if (peek() == '?') {
+ nextChar();
+ auto node = parseOpenTag();
+ expect("?>");
+ if (node->getTag() != "xml") {
+ throw error("invalid declaration");
}
- skipWhitespace();
+ version = node->attr("version", version).getText();
+ encoding = node->attr("encoding", encoding).getText();
+ if (encoding != "utf-8" && encoding != "UTF-8") {
+ throw error("UTF-8 encoding is only supported");
+ }
+ } else {
+ goBack();
}
- skip(2);
- expect(element->getTag());
- expect('>');
+ document = std::make_unique(version, encoding);
}
- //
- else {
- throw error("invalid syntax");
+
+ void parseComment() {
+ expect("!--");
+ if (skipTo("-->")) {
+ skip(3);
+ } else {
+ throw error("comment close missing");
+ }
}
- return element;
+
+ std::string parseText() {
+ size_t start = pos;
+ while (hasNext()) {
+ char c = peek();
+ if (c == '<') {
+ break;
+ }
+ nextChar();
+ }
+ return Parser("[string]", std::string(source.substr(start, pos - start)))
+ .parseString('\0', false);
+ }
+
+ std::string parseXMLName() {
+ char c = peek();
+ if (!is_xml_identifier_start(c)) {
+ throw error("identifier expected");
+ }
+ int start = pos;
+ while (hasNext() && is_xml_identifier_part(source[pos])) {
+ pos++;
+ }
+ return std::string(source.substr(start, pos - start));
+ }
+public:
+ Parser(std::string_view filename, std::string_view source)
+ : BasicParser(filename, source) {
+ }
+
+ std::unique_ptr parse() {
+ parseDeclaration();
+
+ std::unique_ptr root;
+ while (root == nullptr) {
+ root = parseElement();
+ }
+ document->setRoot(std::move(root));
+ return std::move(document);
+ }
+};
}
-xmldocument Parser::parse() {
- parseDeclaration();
-
- xmlelement root = nullptr;
- while (root == nullptr) {
- root = parseElement();
- }
- document->setRoot(root);
- return document;
-}
-
-xmldocument xml::parse(std::string_view filename, std::string_view source) {
+std::unique_ptr xml::parse(
+ std::string_view filename, std::string_view source
+) {
Parser parser(filename, source);
return parser.parse();
}
@@ -354,13 +366,13 @@ inline void newline(
static void stringifyElement(
std::stringstream& ss,
- const xmlelement& element,
+ const Node& element,
bool nice,
const std::string& indentStr,
int indent
) {
- if (element->isText()) {
- std::string text = element->attr("#").getText();
+ if (element.isText()) {
+ std::string text = element.attr("#").getText();
util::replaceAll(text, "&", "&");
util::replaceAll(text, "\"", """);
util::replaceAll(text, "'", "'");
@@ -369,10 +381,10 @@ static void stringifyElement(
ss << text;
return;
}
- const std::string& tag = element->getTag();
+ const std::string& tag = element.getTag();
ss << '<' << tag;
- auto& attrs = element->getAttributes();
+ auto& attrs = element.getAttributes();
if (!attrs.empty()) {
ss << ' ';
int count = 0;
@@ -388,10 +400,10 @@ static void stringifyElement(
count++;
}
}
- auto& elements = element->getElements();
+ auto& elements = element.getElements();
if (elements.size() == 1 && elements[0]->isText()) {
ss << ">";
- stringifyElement(ss, elements[0], nice, indentStr, indent + 1);
+ stringifyElement(ss, *elements[0], nice, indentStr, indent + 1);
ss << "" << tag << ">";
return;
}
@@ -399,7 +411,7 @@ static void stringifyElement(
ss << '>';
for (auto& sub : elements) {
newline(ss, nice, indentStr, indent + 1);
- stringifyElement(ss, sub, nice, indentStr, indent + 1);
+ stringifyElement(ss, *sub, nice, indentStr, indent + 1);
}
newline(ss, nice, indentStr, indent);
ss << "" << tag << ">";
@@ -410,16 +422,16 @@ static void stringifyElement(
}
std::string xml::stringify(
- const xmldocument& document, bool nice, const std::string& indentStr
+ const Document& document, bool nice, const std::string& indentStr
) {
std::stringstream ss;
// XML declaration
- ss << "getVersion();
+ ss << "";
newline(ss, nice, indentStr, 0);
- stringifyElement(ss, document->getRoot(), nice, indentStr, 0);
+ stringifyElement(ss, *document.getRoot(), nice, indentStr, 0);
return ss.str();
}
diff --git a/src/coders/xml.hpp b/src/coders/xml.hpp
index 54a2b589..5831c29a 100644
--- a/src/coders/xml.hpp
+++ b/src/coders/xml.hpp
@@ -13,11 +13,6 @@ namespace xml {
class Attribute;
class Document;
- using xmlattribute = Attribute;
- using xmlelement = std::shared_ptr;
- using xmldocument = std::shared_ptr;
- using xmlelements_map = std::unordered_map;
-
class Attribute {
std::string name;
std::string text;
@@ -40,13 +35,15 @@ namespace xml {
/// 'text'
class Node {
std::string tag;
- std::unordered_map attrs;
- std::vector elements;
+ std::unordered_map attrs;
+ std::vector> elements;
public:
Node(std::string tag);
+ Node(const Node&) = delete;
+
/// @brief Add sub-element
- void add(const xmlelement& element);
+ void add(std::unique_ptr element);
/// @brief Set attribute value. Creates attribute if does not exists
/// @param name attribute name
@@ -67,15 +64,15 @@ namespace xml {
/// @brief Get attribute by name
/// @param name attribute name
/// @throws std::runtime_error if element has no attribute
- /// @return xmlattribute - {name, value}
- const xmlattribute& attr(const std::string& name) const;
+ /// @return xml attribute - {name, value}
+ const Attribute& attr(const std::string& name) const;
/// @brief Get attribute by name
/// @param name attribute name
/// @param def default value will be returned wrapped in xmlattribute
/// if element has no attribute
- /// @return xmlattribute - {name, value} or {name, def} if not found*/
- xmlattribute attr(const std::string& name, const std::string& def)
+ /// @return xml attribute - {name, value} or {name, def} if not found
+ Attribute attr(const std::string& name, const std::string& def)
const;
/// @brief Check if element has attribute
@@ -86,51 +83,37 @@ namespace xml {
/// @param index sub-element index
/// @throws std::out_of_range if an invalid index given
/// @return sub-element
- xmlelement sub(size_t index);
+ Node& sub(size_t index);
+ const Node& sub(size_t index) const;
/// @brief Get number of sub-elements
size_t size() const;
- const std::vector& getElements() const;
- const xmlelements_map& getAttributes() const;
+ const std::vector>& getElements() const;
+ const std::unordered_map& getAttributes() const;
};
class Document {
- xmlelement root = nullptr;
+ std::unique_ptr root = nullptr;
std::string version;
std::string encoding;
public:
Document(std::string version, std::string encoding);
- void setRoot(const xmlelement& element);
- xmlelement getRoot() const;
+ void setRoot(std::unique_ptr element);
+ const Node* getRoot() const;
const std::string& getVersion() const;
const std::string& getEncoding() const;
};
- class Parser : BasicParser {
- xmldocument document;
-
- xmlelement parseOpenTag();
- xmlelement parseElement();
- void parseDeclaration();
- void parseComment();
- std::string parseText();
- std::string parseXMLName();
- public:
- Parser(std::string_view filename, std::string_view source);
-
- xmldocument parse();
- };
-
/// @brief Serialize XML Document to string
/// @param document serializing document
/// @param nice use human readable format (with indents and line-separators)
/// @param indentStr indentation characters sequence (default - 4 spaces)
/// @return XML string
- extern std::string stringify(
- const xmldocument& document,
+ std::string stringify(
+ const Document& document,
bool nice = true,
const std::string& indentStr = " "
);
@@ -139,7 +122,9 @@ namespace xml {
/// @param filename file name will be shown in error messages
/// @param source xml source code string
/// @return xml document
- extern xmldocument parse(
+ std::unique_ptr parse(
std::string_view filename, std::string_view source
);
+
+ using xmlelement = Node;
}
diff --git a/src/devtools/syntax.hpp b/src/devtools/syntax.hpp
new file mode 100644
index 00000000..6c7d4b15
--- /dev/null
+++ b/src/devtools/syntax.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include
+
+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)) {
+ }
+ };
+}
diff --git a/src/devtools/syntax_highlighting.cpp b/src/devtools/syntax_highlighting.cpp
new file mode 100644
index 00000000..f68780df
--- /dev/null
+++ b/src/devtools/syntax_highlighting.cpp
@@ -0,0 +1,72 @@
+#include "syntax_highlighting.hpp"
+
+#include "coders/commons.hpp"
+#include "coders/lua_parsing.hpp"
+#include "graphics/core/Font.hpp"
+
+using namespace devtools;
+
+static std::unique_ptr build_styles(
+ const std::vector& tokens
+) {
+ using devtools::TokenTag;
+ FontStylesScheme styles {
+ {
+ {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 != 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) {
+ int n = token.start.pos - offset;
+ styles.map.insert(styles.map.end(), token.start.pos - offset, 0);
+ }
+ offset = token.end.pos;
+ int styleIndex;
+ switch (token.tag) {
+ 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;
+ }
+ styles.map.insert(
+ styles.map.end(), token.end.pos - token.start.pos, styleIndex
+ );
+ }
+ styles.map.push_back(0);
+ return std::make_unique(std::move(styles));
+}
+
+std::unique_ptr devtools::syntax_highlight(
+ const std::string& lang, std::string_view source
+) {
+ try {
+ if (lang == "lua") {
+ auto tokens = lua::tokenize("", source);
+ return build_styles(tokens);
+ } else {
+ return nullptr;
+ }
+ } catch (const parsing_error& err) {
+ return nullptr;
+ }
+}
diff --git a/src/devtools/syntax_highlighting.hpp b/src/devtools/syntax_highlighting.hpp
new file mode 100644
index 00000000..18db6389
--- /dev/null
+++ b/src/devtools/syntax_highlighting.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+#include
+
+struct FontStylesScheme;
+
+namespace devtools {
+ enum SyntaxStyles {
+ DEFAULT, KEYWORD, LITERAL, COMMENT, ERROR
+ };
+
+ std::unique_ptr syntax_highlight(
+ const std::string& lang, std::string_view source
+ );
+}
diff --git a/src/engine.cpp b/src/engine.cpp
index 31db4537..129dc68d 100644
--- a/src/engine.cpp
+++ b/src/engine.cpp
@@ -206,7 +206,7 @@ void Engine::renderFrame(Batch2D& batch) {
Viewport viewport(Window::width, Window::height);
DrawContext ctx(nullptr, viewport, &batch);
- gui->draw(&ctx, assets.get());
+ gui->draw(ctx, *assets);
}
void Engine::processPostRunnables() {
diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp
index fff98f3f..2108b46e 100644
--- a/src/frontend/UiDocument.cpp
+++ b/src/frontend/UiDocument.cpp
@@ -67,9 +67,7 @@ std::unique_ptr UiDocument::read(
: scripting::create_doc_environment(penv, name);
gui::UiXmlReader reader(env);
- auto view = reader.readXML(
- file.u8string(), xmldoc->getRoot()
- );
+ auto view = reader.readXML(file.u8string(), *xmldoc->getRoot());
view->setId("root");
uidocscript script {};
auto scriptFile = fs::path(file.u8string()+".lua");
diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp
index 30659969..21111e18 100644
--- a/src/frontend/hud.cpp
+++ b/src/frontend/hud.cpp
@@ -124,7 +124,7 @@ std::shared_ptr Hud::createContentAccess() {
});
InventoryBuilder builder;
- builder.addGrid(8, itemsCount-1, glm::vec2(), 8, true, slotLayout);
+ builder.addGrid(8, itemsCount-1, glm::vec2(), glm::vec4(8, 8, 12, 8), true, slotLayout);
auto view = builder.build();
view->bind(accessInventory, content);
view->setMargin(glm::vec4());
@@ -137,7 +137,7 @@ std::shared_ptr Hud::createHotbar() {
SlotLayout slotLayout(-1, glm::vec2(), false, false, nullptr, nullptr, nullptr);
InventoryBuilder builder;
- builder.addGrid(10, 10, glm::vec2(), 4, true, slotLayout);
+ builder.addGrid(10, 10, glm::vec2(), glm::vec4(4), true, slotLayout);
auto view = builder.build();
view->setId("hud.hotbar");
view->setOrigin(glm::vec2(view->getSize().x/2, 0));
@@ -346,9 +346,9 @@ void Hud::update(bool visible) {
element.getNode()->setVisible(visible);
}
- glm::vec2 invSize = contentAccessPanel->getSize();
+ glm::vec2 caSize = contentAccessPanel->getSize();
contentAccessPanel->setVisible(inventoryView != nullptr && showContentPanel);
- contentAccessPanel->setSize(glm::vec2(invSize.x, Window::height));
+ contentAccessPanel->setSize(glm::vec2(caSize.x, Window::height));
contentAccess->setMinSize(glm::vec2(1, Window::height));
hotbarView->setVisible(visible && !(secondUI && !inventoryView));
diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp
index 580ab54d..5594664a 100644
--- a/src/frontend/hud.hpp
+++ b/src/frontend/hud.hpp
@@ -95,16 +95,16 @@ class Hud : public util::ObjectsKeeper {
/// @brief Inventories interaction agent (grabbed item)
std::shared_ptr exchangeSlot;
/// @brief Exchange slot inventory (1 slot only)
- std::shared_ptr exchangeSlotInv = nullptr;
+ std::shared_ptr exchangeSlotInv;
/// @brief List of all controlled hud elements
std::vector elements;
/// @brief Player inventory view
- std::shared_ptr inventoryView = nullptr;
+ std::shared_ptr inventoryView;
/// @brief Block inventory view
- std::shared_ptr blockUI = nullptr;
+ std::shared_ptr blockUI;
/// @brief Secondary inventory view
- std::shared_ptr secondInvView = nullptr;
+ std::shared_ptr secondInvView;
/// @brief Position of the block open
glm::ivec3 blockPos {};
/// @brief Id of the block open (used to detect block destruction or replacement)
@@ -114,9 +114,9 @@ class Hud : public util::ObjectsKeeper {
/// @brief Provide cheat controllers to the debug panel
bool allowDebugCheats = true;
/// @brief UI element will be dynamicly positioned near to inventory or in screen center
- std::shared_ptr secondUI = nullptr;
+ std::shared_ptr secondUI;
- std::shared_ptr debugMinimap = nullptr;
+ std::shared_ptr debugMinimap;
std::unique_ptr debugImgWorldGen;
diff --git a/src/graphics/core/Batch2D.cpp b/src/graphics/core/Batch2D.cpp
index c4c67db2..b7ccf5ff 100644
--- a/src/graphics/core/Batch2D.cpp
+++ b/src/graphics/core/Batch2D.cpp
@@ -261,6 +261,24 @@ void Batch2D::rect(
vertex(x+w, y+h, u+tx, v, r,g,b,a);
}
+void Batch2D::parallelogram(
+ float x, float y, float w, float h, float skew,
+ float u, float v, float tx, float ty,
+ float r, float g, float b, float a
+){
+ if (index + 6*B2D_VERTEX_SIZE >= capacity) {
+ flush();
+ }
+ setPrimitive(DrawPrimitive::triangle);
+ vertex(x-skew*w, y, u, v+ty, r,g,b,a);
+ vertex(x+(1+skew)*w, y+h, u+tx, v, r,g,b,a);
+ vertex(x+skew*w, y+h, u, v, r,g,b,a);
+
+ vertex(x-skew*w, y, u, v+ty, r,g,b,a);
+ vertex(x+w-skew*w, y, u+tx, v+ty, r,g,b,a);
+ vertex(x+(1+skew)*w, y+h, u+tx, v, r,g,b,a);
+}
+
void Batch2D::rect(
float x, float y, float w, float h,
float r0, float g0, float b0,
@@ -336,6 +354,22 @@ void Batch2D::sprite(float x, float y, float w, float h, int atlasRes, int index
rect(x, y, w, h, u, v, scale, scale, tint.r, tint.g, tint.b, tint.a);
}
+void Batch2D::sprite(
+ float x,
+ float y,
+ float w,
+ float h,
+ float skew,
+ int atlasRes,
+ int index,
+ glm::vec4 tint
+) {
+ float scale = 1.0f / (float)atlasRes;
+ float u = (index % atlasRes) * scale;
+ float v = 1.0f - ((index / atlasRes) * scale) - scale;
+ parallelogram(x, y, w, h, skew, u, v, scale, scale, tint.r, tint.g, tint.b, tint.a);
+}
+
void Batch2D::flush() {
if (index == 0)
return;
diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp
index 2877f5bd..6bfdb14d 100644
--- a/src/graphics/core/Batch2D.hpp
+++ b/src/graphics/core/Batch2D.hpp
@@ -45,6 +45,7 @@ public:
void setRegion(UVRegion region);
void sprite(float x, float y, float w, float h, const UVRegion& region, glm::vec4 tint);
void sprite(float x, float y, float w, float h, int atlasRes, int index, glm::vec4 tint);
+ void sprite(float x, float y, float w, float h, float skew, int atlasRes, int index, glm::vec4 tint);
void point(float x, float y, float r, float g, float b, float a);
inline void setColor(glm::vec4 color) {
@@ -79,6 +80,12 @@ public:
float r, float g, float b, float a
);
+ void parallelogram(
+ float x, float y, float w, float h, float skew,
+ float u, float v, float tx, float ty,
+ float r, float g, float b, float a
+ );
+
void rect(
float x, float y, float w, float h,
float r0, float g0, float b0,
diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp
index 9c40809f..2116fdd3 100644
--- a/src/graphics/core/Font.cpp
+++ b/src/graphics/core/Font.cpp
@@ -1,5 +1,6 @@
#include "Font.hpp"
+#include
#include
#include "Texture.hpp"
#include "Batch2D.hpp"
@@ -52,17 +53,21 @@ static inline void draw_glyph(
uint c,
const glm::vec3& right,
const glm::vec3& up,
- float glyphInterval
+ float glyphInterval,
+ const FontStyle& style
) {
- batch.sprite(
- pos.x + offset.x * right.x,
- pos.y + offset.y * right.y,
- right.x / glyphInterval,
- up.y,
- 16,
- c,
- batch.getColor()
- );
+ for (int i = 0; i <= style.bold; i++) {
+ batch.sprite(
+ pos.x + (offset.x + i / (right.x/glyphInterval/2.0f)) * right.x,
+ pos.y + offset.y * right.y,
+ right.x / glyphInterval,
+ up.y,
+ -0.15f * style.italic,
+ 16,
+ c,
+ batch.getColor() * style.color
+ );
+ }
}
static inline void draw_glyph(
@@ -72,17 +77,20 @@ static inline void draw_glyph(
uint c,
const glm::vec3& right,
const glm::vec3& up,
- float glyphInterval
+ float glyphInterval,
+ const FontStyle& style
) {
- batch.sprite(
- pos + right * offset.x + up * offset.y,
- up, right / glyphInterval,
- 0.5f,
- 0.5f,
- 16,
- c,
- batch.getColor()
- );
+ for (int i = 0; i <= style.bold; i++) {
+ batch.sprite(
+ pos + right * (offset.x + i) + up * offset.y,
+ up, right / glyphInterval,
+ 0.5f,
+ 0.5f,
+ 16,
+ c,
+ batch.getColor() * style.color
+ );
+ }
}
template
@@ -93,14 +101,32 @@ 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
) {
+ static FontStylesScheme defStyles {{{}}, {0}};
+
+ if (styles == nullptr) {
+ styles = &defStyles;
+ }
+
uint page = 0;
uint next = MAX_CODEPAGES;
int x = 0;
int y = 0;
+ bool hasLines = false;
+
do {
- for (uint c : text){
+ 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);
+ hasLines |= style.strikethrough;
+ hasLines |= style.underline;
+
if (!font.isPrintableChar(c)) {
x++;
continue;
@@ -109,7 +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
+ batch, pos, glm::vec2(x, y), c, right, up, interval, style
);
}
else if (charpage > page && charpage < next){
@@ -121,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 {
@@ -135,20 +186,30 @@ const Texture* Font::getPage(int charpage) const {
}
void Font::draw(
- Batch2D& batch, std::wstring_view text, int x, int y, float scale
+ Batch2D& batch,
+ std::wstring_view text,
+ int x,
+ int y,
+ const FontStylesScheme* styles,
+ size_t styleMapOffset,
+ float scale
) const {
draw_text(
*this, batch, text,
glm::vec3(x, y, 0),
glm::vec3(glyphInterval*scale, 0, 0),
glm::vec3(0, lineHeight*scale, 0),
- glyphInterval/static_cast(lineHeight)
+ glyphInterval/static_cast(lineHeight),
+ styles,
+ styleMapOffset
);
}
void Font::draw(
Batch3D& batch,
std::wstring_view text,
+ const FontStylesScheme* styles,
+ size_t styleMapOffset,
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up
@@ -157,6 +218,8 @@ void Font::draw(
*this, batch, text, pos,
right * static_cast(glyphInterval),
up * static_cast(lineHeight),
- glyphInterval/static_cast(lineHeight)
+ glyphInterval/static_cast(lineHeight),
+ styles,
+ styleMapOffset
);
}
diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp
index deb07534..07fe0532 100644
--- a/src/graphics/core/Font.hpp
+++ b/src/graphics/core/Font.hpp
@@ -11,10 +11,33 @@ class Batch2D;
class Batch3D;
class Camera;
-enum class FontStyle {
- none,
- shadow,
- outline
+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 {
+ std::vector palette;
+ std::vector map;
};
class Font {
@@ -45,12 +68,22 @@ public:
/// @brief Check if character is visible (non-whitespace)
/// @param codepoint character unicode codepoint
bool isPrintableChar(uint codepoint) const;
-
- void draw(Batch2D& batch, std::wstring_view text, int x, int y, float scale=1) const;
+
+ void draw(
+ Batch2D& batch,
+ std::wstring_view text,
+ int x,
+ int y,
+ const FontStylesScheme* styles,
+ size_t styleMapOffset,
+ float scale = 1
+ ) const;
void draw(
Batch3D& batch,
std::wstring_view text,
+ const FontStylesScheme* styles,
+ size_t styleMapOffset,
const glm::vec3& pos,
const glm::vec3& right={1, 0, 0},
const glm::vec3& up={0, 1, 0}
diff --git a/src/graphics/render/TextsRenderer.cpp b/src/graphics/render/TextsRenderer.cpp
index fa24b61b..32681ce5 100644
--- a/src/graphics/render/TextsRenderer.cpp
+++ b/src/graphics/render/TextsRenderer.cpp
@@ -103,6 +103,8 @@ void TextsRenderer::renderNote(
font.draw(
batch,
text,
+ nullptr,
+ 0,
pos - xvec * (width * 0.5f) * preset.scale,
xvec * preset.scale,
yvec * preset.scale
diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp
index 40b74a4c..38bc2257 100644
--- a/src/graphics/ui/GUI.cpp
+++ b/src/graphics/ui/GUI.cpp
@@ -197,18 +197,18 @@ void GUI::act(float delta, const Viewport& vp) {
}
}
-void GUI::draw(const DrawContext* pctx, Assets* assets) {
- auto& viewport = pctx->getViewport();
+void GUI::draw(const DrawContext& pctx, const Assets& assets) {
+ auto& viewport = pctx.getViewport();
glm::vec2 wsize = viewport.size();
menu->setPos((wsize - menu->getSize()) / 2.0f);
uicamera->setFov(wsize.y);
- auto uishader = assets->get("ui");
+ auto uishader = assets.get("ui");
uishader->use();
uishader->uniformMatrix("u_projview", uicamera->getProjView());
- pctx->getBatch2D()->begin();
+ pctx.getBatch2D()->begin();
container->draw(pctx, assets);
}
diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp
index b2b20bd0..c0dec649 100644
--- a/src/graphics/ui/GUI.hpp
+++ b/src/graphics/ui/GUI.hpp
@@ -94,7 +94,7 @@ namespace gui {
/// @brief Draw all visible elements on main container
/// @param pctx parent graphics context
/// @param assets active assets storage
- void draw(const DrawContext* pctx, Assets* assets);
+ void draw(const DrawContext& pctx, const Assets& assets);
/// @brief Add element to the main container
/// @param node UI element
diff --git a/src/graphics/ui/elements/Button.cpp b/src/graphics/ui/elements/Button.cpp
index b6a11232..dfdf864d 100644
--- a/src/graphics/ui/elements/Button.cpp
+++ b/src/graphics/ui/elements/Button.cpp
@@ -52,7 +52,7 @@ Button::Button(
void Button::setText(std::wstring text) {
if (label) {
- label->setText(text);
+ label->setText(std::move(text));
}
}
@@ -77,9 +77,9 @@ void Button::refresh() {
}
}
-void Button::drawBackground(const DrawContext* pctx, Assets*) {
+void Button::drawBackground(const DrawContext& pctx, const Assets&) {
glm::vec2 pos = calcPos();
- auto batch = pctx->getBatch2D();
+ auto batch = pctx.getBatch2D();
batch->texture(nullptr);
batch->setColor(calcColor());
batch->rect(pos.x, pos.y, size.x, size.y);
diff --git a/src/graphics/ui/elements/Button.hpp b/src/graphics/ui/elements/Button.hpp
index 61bfea03..39b23426 100644
--- a/src/graphics/ui/elements/Button.hpp
+++ b/src/graphics/ui/elements/Button.hpp
@@ -7,7 +7,7 @@ namespace gui {
class Button : public Panel {
protected:
- std::shared_ptr