Merge pull request #400 from MihailRis/syntax-hightlighting

Text Formatting
This commit is contained in:
MihailRis 2024-12-06 22:21:16 +03:00 committed by GitHub
commit 61b73bfabd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 1593 additions and 550 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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("<image src='test'/>")` |
| 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("<image src='test'/>")` |
| 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 |

21
doc/en/text-styles.md Normal file
View File

@ -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__` | <ins>Underlined text</ins> |
| Strikethrough | `~~Strikethrough text~~` | ~~Strikethrough text~~ |
Styles can be combined. Example:
```md
***__Message__*** using *~~combed~~ combined* styles__~~.~~__
```
Output:
***<ins>Message</ins>*** using *~~combed~~ combined* styles<ins>~~.~~</ins>

View File

@ -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*

View File

@ -19,4 +19,5 @@
- [Свойства блоков](block-properties.md)
- [Свойства предметов](item-properties.md)
- [Скриптинг](scripting.md)
- [Стили текста](text-styles.md)
- [Частицы](particles.md)

View File

@ -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
```
Экранирует разметку в тексте.

View File

@ -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)

21
doc/ru/text-styles.md Normal file
View File

@ -0,0 +1,21 @@
# Стили текста
Для разметки стилей текста используется собственный диалект Markdown.
Форматирование работает на UI элементах: label и textbox, если явно указано `markup="md"`.
## Стили
| Стиль | Пример | Вывод |
| ------------ | ------------------------- | ----------------------------- |
| Жирный | `**Жирный шрифт**` | **Жирный шрифт** |
| Курсив | `*Текст курсивом*` | *Текст курсивом* |
| Подчеркнутый | `__Подчеркнутый текст__` | <ins>Подчеркнутый текст</ins> |
| Зачеркнутый | `~~Зачеркнутый текст~~` | ~~Зачеркнутый текст~~ |
Стили могут объединяться. Пример:
```md
***__Сообщение__***, демонстрирующее *~~обедненные~~ объединенные* стили__~~.~~__
```
Вывод:
***<ins>Сообщение</ins>***, демонстрирующее *~~обедненные~~ объединенные* стили<ins>~~.~~</ins>

View File

@ -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*

View File

@ -22,6 +22,7 @@
multiline='true'
size-func="gui.get_viewport()[1],40"
gravity="bottom-left"
markup="md"
></textbox>
</container>
<container id="editorContainer" pos="0,60" color="#00000080"
@ -32,10 +33,9 @@
autoresize='true'
margin='0'
padding='5'
editable='false'
multiline='true'
line-numbers='true'
text-color="#FFFFFFA0"
syntax='lua'
size-func="gui.get_viewport()[1]-350,40"
gravity="top-left"
text-wrap='false'
@ -55,6 +55,7 @@
<textbox id='prompt'
consumer='submit'
margin='0'
markup="md"
gravity='bottom-left'
size-func="gui.get_viewport()[1],40"
onup="on_history_up()"

View File

@ -214,6 +214,28 @@ std::string_view BasicParser::readUntil(char c) {
return source.substr(start, pos - start);
}
std::string_view BasicParser::readUntil(std::string_view s, bool nothrow) {
int start = pos;
size_t found = source.find(s, pos);
if (found == std::string::npos) {
if (nothrow) {
pos = source.size();
return source.substr(start);
}
throw error(util::quote(std::string(s))+" expected");
}
skip(found - pos);
return source.substr(start, pos - start);
}
std::string_view BasicParser::readUntilWhitespace() {
int start = pos;
while (hasNext() && !is_whitespace(source[pos])) {
pos++;
}
return source.substr(start, pos - start);
}
std::string_view BasicParser::readUntilEOL() {
int start = pos;
while (hasNext() && source[pos] != '\r' && source[pos] != '\n') {

View File

@ -105,6 +105,8 @@ protected:
parsing_error error(const std::string& message);
public:
std::string_view readUntil(char c);
std::string_view readUntil(std::string_view s, bool nothrow);
std::string_view readUntilWhitespace();
std::string_view readUntilEOL();
std::string parseName();
std::string parseXmlName();

View File

@ -11,15 +11,17 @@
using namespace json;
class Parser : BasicParser {
dv::value parseList();
dv::value parseObject();
dv::value parseValue();
public:
Parser(std::string_view filename, std::string_view source);
namespace {
class Parser : BasicParser {
dv::value parseList();
dv::value parseObject();
dv::value parseValue();
public:
Parser(std::string_view filename, std::string_view source);
dv::value parse();
};
dv::value parse();
};
}
inline void newline(
std::stringstream& ss, bool nice, uint indent, const std::string& indentstr

188
src/coders/lua_parsing.cpp Normal file
View File

@ -0,0 +1,188 @@
#include "lua_parsing.hpp"
#include <set>
#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",
"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<Token> 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<int>(pos),
static_cast<int>(linestart),
static_cast<int>(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<Token> 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<Token> lua::tokenize(std::string_view file, std::string_view source) {
return Tokenizer(file, source).tokenize();
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <vector>
#include "devtools/syntax.hpp"
namespace lua {
bool is_lua_keyword(std::string_view view);
std::vector<devtools::Token> tokenize(
std::string_view file, std::string_view source
);
}

View File

@ -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<Node> 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<xmlelement>& Node::getElements() const {
const std::vector<std::unique_ptr<Node>>& Node::getElements() const {
return elements;
}
const xmlelements_map& Node::getAttributes() const {
const std::unordered_map<std::string, Attribute>& 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<Node> 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<Node>(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<Document>(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> document;
xmlelement Parser::parseElement() {
// text element
if (peek() != '<') {
auto element = std::make_shared<Node>("#");
auto text = parseText();
util::replaceAll(text, "&quot;", "\"");
util::replaceAll(text, "&apos;", "'");
util::replaceAll(text, "&lt;", "<");
util::replaceAll(text, "&gt;", ">");
util::replaceAll(text, "&amp;", "&");
element->set("#", text);
std::unique_ptr<Node> parseOpenTag() {
std::string tag = parseXMLName();
auto node = std::make_unique<Node>(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<Node> parseElement() {
// text element
if (peek() != '<') {
auto element = std::make_unique<Node>("#");
auto text = parseText();
util::replaceAll(text, "&quot;", "\"");
util::replaceAll(text, "&apos;", "'");
util::replaceAll(text, "&lt;", "<");
util::replaceAll(text, "&gt;", ">");
util::replaceAll(text, "&amp;", "&");
element->set("#", text);
return element;
}
nextChar();
// <!--element-->
if (peek() == '!') {
if (isNext("!DOCTYPE ")) {
throw error("XML DTD is not supported yet");
}
parseComment();
return nullptr;
}
auto element = parseOpenTag();
char c = nextChar();
// <element/>
if (c == '/') {
expect('>');
}
// <element>...</element>
else if (c == '>') {
skipWhitespace();
while (!isNext("</")) {
auto sub = parseElement();
if (sub) {
element->add(std::move(sub));
}
skipWhitespace();
}
skip(2);
expect(element->getTag());
expect('>');
}
// <element?>
else {
throw error("invalid syntax");
}
return element;
}
nextChar();
// <!--element-->
if (peek() == '!') {
if (isNext("!DOCTYPE ")) {
throw error("XML DTD is not supported yet");
}
parseComment();
return nullptr;
}
auto element = parseOpenTag();
char c = nextChar();
// <element/>
if (c == '/') {
expect('>');
}
// <element>...</element>
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<Document>(version, encoding);
}
// <element?>
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<Document> parse() {
parseDeclaration();
std::unique_ptr<Node> 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<Document> 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, "&", "&amp;");
util::replaceAll(text, "\"", "&quot;");
util::replaceAll(text, "'", "&apos;");
@ -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 << "<?xml version=\"" << document->getVersion();
ss << "<?xml version=\"" << document.getVersion();
ss << "\" encoding=\"UTF-8\" ?>";
newline(ss, nice, indentStr, 0);
stringifyElement(ss, document->getRoot(), nice, indentStr, 0);
stringifyElement(ss, *document.getRoot(), nice, indentStr, 0);
return ss.str();
}

View File

@ -13,11 +13,6 @@ namespace xml {
class Attribute;
class Document;
using xmlattribute = Attribute;
using xmlelement = std::shared_ptr<Node>;
using xmldocument = std::shared_ptr<Document>;
using xmlelements_map = std::unordered_map<std::string, xmlattribute>;
class Attribute {
std::string name;
std::string text;
@ -40,13 +35,15 @@ namespace xml {
/// 'text'
class Node {
std::string tag;
std::unordered_map<std::string, xmlattribute> attrs;
std::vector<xmlelement> elements;
std::unordered_map<std::string, Attribute> attrs;
std::vector<std::unique_ptr<Node>> elements;
public:
Node(std::string tag);
Node(const Node&) = delete;
/// @brief Add sub-element
void add(const xmlelement& element);
void add(std::unique_ptr<Node> 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<xmlelement>& getElements() const;
const xmlelements_map& getAttributes() const;
const std::vector<std::unique_ptr<Node>>& getElements() const;
const std::unordered_map<std::string, Attribute>& getAttributes() const;
};
class Document {
xmlelement root = nullptr;
std::unique_ptr<Node> 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<Node> 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<Document> parse(
std::string_view filename, std::string_view source
);
using xmlelement = Node;
}

30
src/devtools/syntax.hpp Normal file
View 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)) {
}
};
}

View File

@ -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<FontStylesScheme> build_styles(
const std::vector<devtools::Token>& 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<FontStylesScheme>(std::move(styles));
}
std::unique_ptr<FontStylesScheme> devtools::syntax_highlight(
const std::string& lang, std::string_view source
) {
try {
if (lang == "lua") {
auto tokens = lua::tokenize("<string>", source);
return build_styles(tokens);
} else {
return nullptr;
}
} catch (const parsing_error& err) {
return nullptr;
}
}

View File

@ -0,0 +1,16 @@
#pragma once
#include <string>
#include <memory>
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
);
}

View File

@ -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() {

View File

@ -67,9 +67,7 @@ std::unique_ptr<UiDocument> 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");

View File

@ -124,7 +124,7 @@ std::shared_ptr<InventoryView> 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<InventoryView> 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));

View File

@ -95,16 +95,16 @@ class Hud : public util::ObjectsKeeper {
/// @brief Inventories interaction agent (grabbed item)
std::shared_ptr<gui::SlotView> exchangeSlot;
/// @brief Exchange slot inventory (1 slot only)
std::shared_ptr<Inventory> exchangeSlotInv = nullptr;
std::shared_ptr<Inventory> exchangeSlotInv;
/// @brief List of all controlled hud elements
std::vector<HudElement> elements;
/// @brief Player inventory view
std::shared_ptr<gui::InventoryView> inventoryView = nullptr;
std::shared_ptr<gui::InventoryView> inventoryView;
/// @brief Block inventory view
std::shared_ptr<gui::InventoryView> blockUI = nullptr;
std::shared_ptr<gui::InventoryView> blockUI;
/// @brief Secondary inventory view
std::shared_ptr<gui::InventoryView> secondInvView = nullptr;
std::shared_ptr<gui::InventoryView> 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<gui::UINode> secondUI = nullptr;
std::shared_ptr<gui::UINode> secondUI;
std::shared_ptr<gui::UINode> debugMinimap = nullptr;
std::shared_ptr<gui::UINode> debugMinimap;
std::unique_ptr<ImageData> debugImgWorldGen;

View File

@ -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;

View File

@ -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,

View File

@ -1,5 +1,6 @@
#include "Font.hpp"
#include <limits>
#include <utility>
#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 <class Batch>
@ -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<float>(lineHeight)
glyphInterval/static_cast<float>(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<float>(glyphInterval),
up * static_cast<float>(lineHeight),
glyphInterval/static_cast<float>(lineHeight)
glyphInterval/static_cast<float>(lineHeight),
styles,
styleMapOffset
);
}

View File

@ -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<FontStyle> palette;
std::vector<ubyte> map;
};
class Font {
@ -46,11 +69,21 @@ public:
/// @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}

View File

@ -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

View File

@ -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<Shader>("ui");
auto uishader = assets.get<Shader>("ui");
uishader->use();
uishader->uniformMatrix("u_projview", uicamera->getProjView());
pctx->getBatch2D()->begin();
pctx.getBatch2D()->begin();
container->draw(pctx, assets);
}

View File

@ -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

View File

@ -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);

View File

@ -7,7 +7,7 @@ namespace gui {
class Button : public Panel {
protected:
std::shared_ptr<Label> label = nullptr;
std::shared_ptr<Label> label;
public:
Button(const std::shared_ptr<UINode>& content,
glm::vec4 padding=glm::vec4(2.0f));
@ -17,7 +17,9 @@ namespace gui {
const onaction& action,
glm::vec2 size=glm::vec2(-1));
virtual void drawBackground(const DrawContext* pctx, Assets* assets) override;
virtual void drawBackground(
const DrawContext& pctx, const Assets& assets
) override;
virtual Align getTextAlign() const;
virtual void setTextAlign(Align align);

View File

@ -13,12 +13,12 @@ CheckBox::CheckBox(bool checked) : UINode(glm::vec2(32.0f)), checked(checked) {
setHoverColor({0.05f, 0.1f, 0.2f, 0.75f});
}
void CheckBox::draw(const DrawContext* pctx, Assets*) {
void CheckBox::draw(const DrawContext& pctx, const Assets&) {
if (supplier) {
checked = supplier();
}
glm::vec2 pos = calcPos();
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
batch->setColor(checked ? checkColor : calcColor());
batch->rect(pos.x, pos.y, size.x, size.y);

View File

@ -15,7 +15,7 @@ namespace gui {
public:
CheckBox(bool checked=false);
virtual void draw(const DrawContext* pctx, Assets* assets) override;
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void mouseRelease(GUI*, int x, int y) override;

View File

@ -17,11 +17,19 @@ Container::~Container() {
Container::clear();
}
std::shared_ptr<UINode> Container::getAt(glm::vec2 pos, std::shared_ptr<UINode> self) {
std::shared_ptr<UINode> Container::getAt(
const glm::vec2& pos, const std::shared_ptr<UINode>& self
) {
if (!isInteractive() || !isEnabled()) {
return nullptr;
}
if (!isInside(pos)) return nullptr;
if (!isInside(pos)) {
return nullptr;
}
int diff = (actualLength-size.y);
if (scrollable && diff > 0 && pos.x > calcPos().x + getSize().x - scrollBarWidth) {
return UINode::getAt(pos, self);
}
for (int i = nodes.size()-1; i >= 0; i--) {
auto& node = nodes[i];
@ -35,6 +43,35 @@ std::shared_ptr<UINode> Container::getAt(glm::vec2 pos, std::shared_ptr<UINode>
return UINode::getAt(pos, self);
}
void Container::mouseMove(GUI* gui, int x, int y) {
UINode::mouseMove(gui, x, y);
if (!scrollable) {
return;
}
auto pos = calcPos();
x -= pos.x;
y -= pos.y;
if (prevScrollY == -1) {
if (x >= size.x - scrollBarWidth) {
prevScrollY = y;
}
return;
}
int diff = (actualLength-size.y);
if (diff > 0) {
scroll -= (y - prevScrollY) / static_cast<float>(size.y) * actualLength;
scroll = -glm::min(
glm::max(static_cast<float>(-scroll), 0.0f), actualLength - size.y
);
}
prevScrollY = y;
}
void Container::mouseRelease(GUI* gui, int x, int y) {
UINode::mouseRelease(gui, x, y);
prevScrollY = -1;
}
void Container::act(float delta) {
for (const auto& node : nodes) {
if (node->isVisible()) {
@ -80,38 +117,50 @@ void Container::setScrollable(bool flag) {
scrollable = flag;
}
void Container::draw(const DrawContext* pctx, Assets* assets) {
void Container::draw(const DrawContext& pctx, const Assets& assets) {
glm::vec2 pos = calcPos();
glm::vec2 size = getSize();
drawBackground(pctx, assets);
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
if (!nodes.empty()) {
batch->flush();
DrawContext ctx = pctx->sub();
DrawContext ctx = pctx.sub();
ctx.setScissors(glm::vec4(pos.x, pos.y, glm::ceil(size.x), glm::ceil(size.y)));
for (const auto& node : nodes) {
if (node->isVisible())
node->draw(pctx, assets);
}
int diff = (actualLength-size.y);
if (scrollable && diff > 0) {
int h = glm::max(size.y / actualLength * size.y, scrollBarWidth / 2.0f);
batch->untexture();
batch->setColor(glm::vec4(1, 1, 1, 0.3f));
batch->rect(
pos.x + size.x - scrollBarWidth,
pos.y - scroll / static_cast<float>(diff) * (size.y - h),
scrollBarWidth, h
);
}
batch->flush();
}
}
void Container::drawBackground(const DrawContext* pctx, Assets*) {
void Container::drawBackground(const DrawContext& pctx, const Assets&) {
glm::vec4 color = calcColor();
if (color.a <= 0.001f)
return;
glm::vec2 pos = calcPos();
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
batch->setColor(color);
batch->rect(pos.x, pos.y, glm::ceil(size.x), glm::ceil(size.y));
}
void Container::add(const std::shared_ptr<UINode> &node) {
void Container::add(const std::shared_ptr<UINode>& node) {
nodes.push_back(node);
node->setParent(this);
node->reposition();

View File

@ -7,23 +7,29 @@
namespace gui {
class Container : public UINode {
int prevScrollY = -1;
protected:
std::vector<std::shared_ptr<UINode>> nodes;
std::vector<IntervalEvent> intervalEvents;
int scroll = 0;
int scrollStep = 40;
int scrollBarWidth = 10;
int actualLength = 0;
bool scrollable = true;
bool isScrolling() {
return prevScrollY != -1;
}
public:
Container(glm::vec2 size);
virtual ~Container();
virtual void act(float delta) override;
virtual void drawBackground(const DrawContext* pctx, Assets* assets);
virtual void draw(const DrawContext* pctx, Assets* assets) override;
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void add(const std::shared_ptr<UINode> &node);
virtual void add(const std::shared_ptr<UINode> &node, glm::vec2 pos);
virtual void drawBackground(const DrawContext& pctx, const Assets& assets);
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
virtual std::shared_ptr<UINode> getAt(const glm::vec2& pos, const std::shared_ptr<UINode>& self) override;
virtual void add(const std::shared_ptr<UINode>& node);
virtual void add(const std::shared_ptr<UINode>& node, glm::vec2 pos);
virtual void clear();
virtual void remove(const std::shared_ptr<UINode>& node);
virtual void remove(const std::string& id);
@ -36,6 +42,9 @@ namespace gui {
virtual void setScrollStep(int step);
virtual void refresh() override;
virtual void mouseMove(GUI*, int x, int y) override;
virtual void mouseRelease(GUI*, int x, int y) override;
const std::vector<std::shared_ptr<UINode>>& getNodes() const;
};
}

View File

@ -15,21 +15,21 @@ Image::Image(std::string texture, glm::vec2 size) : UINode(size), texture(std::m
setInteractive(false);
}
void Image::draw(const DrawContext* pctx, Assets* assets) {
void Image::draw(const DrawContext& pctx, const Assets& assets) {
glm::vec2 pos = calcPos();
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
Texture* texture = nullptr;
auto separator = this->texture.find(':');
if (separator == std::string::npos) {
texture = assets->get<Texture>(this->texture);
texture = assets.get<Texture>(this->texture);
batch->texture(texture);
if (texture && autoresize) {
setSize(glm::vec2(texture->getWidth(), texture->getHeight()));
}
} else {
auto atlasName = this->texture.substr(0, separator);
if (auto atlas = assets->get<Atlas>(atlasName)) {
if (auto atlas = assets.get<Atlas>(atlasName)) {
if (auto region = atlas->getIf(this->texture.substr(separator+1))) {
texture = atlas->getTexture();
batch->texture(atlas->getTexture());

View File

@ -10,7 +10,7 @@ namespace gui {
public:
Image(std::string texture, glm::vec2 size=glm::vec2(32,32));
virtual void draw(const DrawContext* pctx, Assets* assets) override;
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void setAutoResize(bool flag);
virtual bool isAutoResize() const;

View File

@ -9,15 +9,15 @@ using namespace gui;
InputBindBox::InputBindBox(Binding& binding, glm::vec4 padding)
: Panel(glm::vec2(100,32), padding, 0),
binding(binding) {
label = std::make_shared<Label>(L"");
binding(binding),
label(std::make_shared<Label>(L"")) {
add(label);
setScrollable(false);
}
void InputBindBox::drawBackground(const DrawContext* pctx, Assets*) {
void InputBindBox::drawBackground(const DrawContext& pctx, const Assets&) {
glm::vec2 pos = calcPos();
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
batch->setColor(isFocused() ? focusedColor : calcColor());
batch->rect(pos.x, pos.y, size.x, size.y);

View File

@ -13,7 +13,10 @@ namespace gui {
Binding& binding;
public:
InputBindBox(Binding& binding, glm::vec4 padding=glm::vec4(6.0f));
virtual void drawBackground(const DrawContext* pctx, Assets* assets) override;
virtual void drawBackground(
const DrawContext& pctx, const Assets& assets
) override;
virtual void clicked(GUI*, mousecode button) override;
virtual void keyPressed(keycode key) override;

View File

@ -54,7 +54,7 @@ InventoryBuilder::InventoryBuilder() {
void InventoryBuilder::addGrid(
int cols, int count,
glm::vec2 pos,
int padding,
glm::vec4 padding,
bool addpanel,
const SlotLayout& slotLayout
) {
@ -63,8 +63,8 @@ void InventoryBuilder::addGrid(
int rows = ceildiv(count, cols);
uint width = cols * (slotSize + interval) - interval + padding*2;
uint height = rows * (slotSize + interval) - interval + padding*2;
uint width = cols * (slotSize + interval) - interval + padding.x + padding.z;
uint height = rows * (slotSize + interval) - interval + padding.y + padding.w;
glm::vec2 vsize = view->getSize();
if (pos.x + width > vsize.x) {
@ -87,8 +87,8 @@ void InventoryBuilder::addGrid(
break;
}
glm::vec2 position (
col * (slotSize + interval) + padding,
row * (slotSize + interval) + padding
col * (slotSize + interval) + padding.x,
row * (slotSize + interval) + padding.y
);
auto builtSlot = slotLayout;
builtSlot.index = row * cols + col;
@ -115,7 +115,7 @@ SlotView::SlotView(
setTooltipDelay(0.0f);
}
void SlotView::draw(const DrawContext* pctx, Assets* assets) {
void SlotView::draw(const DrawContext& pctx, const Assets& assets) {
if (bound == nullptr) {
return;
}
@ -144,7 +144,7 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) {
color = glm::vec4(1, 1, 1, 0.2f);
}
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->setColor(color);
if (color.a > 0.0) {
batch->texture(nullptr);
@ -157,7 +157,7 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) {
batch->setColor(glm::vec4(1.0f));
auto previews = assets->get<Atlas>("block-previews");
auto previews = assets.get<Atlas>("block-previews");
auto indices = content->getIndices();
auto& item = indices->items.require(stack.getItemId());
@ -176,7 +176,7 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) {
}
case ItemIconType::SPRITE: {
auto textureRegion =
util::get_texture_region(*assets, item.icon, "blocks:notfound");
util::get_texture_region(assets, item.icon, "blocks:notfound");
batch->texture(textureRegion.texture);
batch->rect(
@ -187,16 +187,16 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) {
}
if (stack.getCount() > 1) {
auto font = assets->get<Font>("normal");
auto font = assets.get<Font>("normal");
std::wstring text = std::to_wstring(stack.getCount());
int x = pos.x+slotSize-text.length()*8;
int y = pos.y+slotSize-16;
batch->setColor({0, 0, 0, 1.0f});
font->draw(*batch, text, x+1, y+1);
font->draw(*batch, text, x+1, y+1, nullptr, 0);
batch->setColor(glm::vec4(1.0f));
font->draw(*batch, text, x, y);
font->draw(*batch, text, x, y, nullptr, 0);
}
}

View File

@ -64,7 +64,7 @@ namespace gui {
public:
SlotView(SlotLayout layout);
virtual void draw(const DrawContext* pctx, Assets* assets) override;
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
void setHighlighted(bool flag);
bool isHighlighted() const;
@ -136,7 +136,7 @@ namespace gui {
void addGrid(
int cols, int count,
glm::vec2 pos,
int padding,
glm::vec4 padding,
bool addpanel,
const SlotLayout& slotLayout
);

View File

@ -6,6 +6,7 @@
#include "graphics/core/Font.hpp"
#include "assets/Assets.hpp"
#include "util/stringutil.hpp"
#include "../markdown.hpp"
using namespace gui;
@ -66,6 +67,8 @@ Label::Label(const std::wstring& text, std::string fontName)
cache.update(this->text, multiline, textWrap);
}
Label::~Label() = default;
glm::vec2 Label::calcSize() {
auto font = cache.font;
uint lineHeight = font->getLineHeight();
@ -78,11 +81,16 @@ glm::vec2 Label::calcSize() {
);
}
void Label::setText(const std::wstring& text) {
void Label::setText(std::wstring text) {
if (markup == "md") {
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) {
@ -156,9 +164,9 @@ uint Label::getLinesNumber() const {
return cache.lines.size();
}
void Label::draw(const DrawContext* pctx, Assets* assets) {
auto batch = pctx->getBatch2D();
auto font = assets->get<Font>(fontName);
void Label::draw(const DrawContext& pctx, const Assets& assets) {
auto batch = pctx.getBatch2D();
auto font = assets.get<Font>(fontName);
cache.prepare(font, static_cast<size_t>(glm::abs(getSize().x)));
if (supplier) {
@ -201,10 +209,10 @@ void Label::draw(const DrawContext* pctx, Assets* assets) {
if (i < cache.lines.size()-1) {
view = std::wstring_view(text.c_str()+offset, cache.lines.at(i+1).offset-offset);
}
font->draw(*batch, view, pos.x, pos.y + i * totalLineHeight);
font->draw(*batch, view, pos.x, pos.y + i * totalLineHeight, styles.get(), offset);
}
} else {
font->draw(*batch, text, pos.x, pos.y);
font->draw(*batch, text, pos.x, pos.y, styles.get(), 0);
}
}
@ -239,3 +247,16 @@ void Label::setTextWrapping(bool flag) {
bool Label::isTextWrapping() const {
return textWrap;
}
void Label::setMarkup(std::string_view lang) {
markup = lang;
setText(text);
}
const std::string& Label::getMarkup() const {
return markup;
}
void Label::setStyles(std::unique_ptr<FontStylesScheme> styles) {
this->styles = std::move(styles);
}

View File

@ -3,6 +3,7 @@
#include "UINode.hpp"
class Font;
struct FontStylesScheme;
namespace gui {
struct LineScheme {
@ -51,11 +52,18 @@ namespace gui {
/// @brief Auto resize label to fit text
bool autoresize = false;
/// @brief Text markup language
std::string markup;
std::unique_ptr<FontStylesScheme> styles;
public:
Label(const std::string& text, std::string fontName="normal");
Label(const std::wstring& text, std::string fontName="normal");
virtual void setText(const std::wstring& text);
virtual ~Label();
virtual void setText(std::wstring text);
const std::wstring& getText() const;
virtual void setFontName(std::string name);
@ -95,7 +103,7 @@ namespace gui {
virtual uint getLinesNumber() const;
virtual bool isFakeLine(size_t line) const;
virtual void draw(const DrawContext* pctx, Assets* assets) override;
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void textSupplier(wstringsupplier supplier);
@ -107,5 +115,10 @@ namespace gui {
virtual void setTextWrapping(bool flag);
virtual bool isTextWrapping() const;
virtual void setMarkup(std::string_view lang);
virtual const std::string& getMarkup() const;
virtual void setStyles(std::unique_ptr<FontStylesScheme> styles);
};
}

View File

@ -7,7 +7,7 @@
namespace gui {
struct Page {
std::string name;
std::shared_ptr<UINode> panel = nullptr;
std::shared_ptr<UINode> panel;
};
using page_loader_func = std::function<std::shared_ptr<UINode>(const std::string& name)>;
@ -31,13 +31,13 @@ namespace gui {
/// @param history previous page will not be saved in history if false
void setPage(const std::string &name, bool history=true);
void setPage(Page page, bool history=true);
void addPage(const std::string& name, const std::shared_ptr<UINode> &panel);
void addPage(const std::string& name, const std::shared_ptr<UINode>& panel);
std::shared_ptr<UINode> fetchPage(const std::string& name);
/// @brief Add page supplier used if page is not found
/// @param name page name
/// @param pageSupplier page supplier function
void addSupplier(const std::string &name, const supplier<std::shared_ptr<UINode>> &pageSupplier);
void addSupplier(const std::string& name, const supplier<std::shared_ptr<UINode>>& pageSupplier);
/// @brief Page loader is called if accessed page is not found
void setPageLoader(page_loader_func loader);

View File

@ -23,7 +23,7 @@ namespace gui {
virtual void setOrientation(Orientation orientation);
Orientation getOrientation() const;
virtual void add(const std::shared_ptr<UINode> &node) override;
virtual void add(const std::shared_ptr<UINode>& node) override;
virtual void refresh() override;
virtual void fullRefresh() override;

View File

@ -14,9 +14,9 @@ void Plotter::act(float delta) {
points[index % dmwidth] = std::min(value, dmheight);
}
void Plotter::draw(const DrawContext* pctx, Assets* assets) {
void Plotter::draw(const DrawContext& pctx, const Assets& assets) {
glm::vec2 pos = calcPos();
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
batch->lineWidth(1);
for (int i = index+1; i < index+dmwidth; i++) {
@ -37,7 +37,7 @@ void Plotter::draw(const DrawContext* pctx, Assets* assets) {
}
int current_point = static_cast<int>(points[index % dmwidth]);
auto font = assets->get<Font>("normal");
auto font = assets.get<Font>("normal");
for (int y = 0; y < dmheight; y += labelsInterval) {
std::wstring string;
if (current_point/16 == y/labelsInterval) {
@ -47,6 +47,13 @@ void Plotter::draw(const DrawContext* pctx, Assets* assets) {
batch->setColor({1,1,1,0.2f});
string = util::to_wstring(y / multiplier, 3);
}
font->draw(*batch, string, pos.x+dmwidth+2, pos.y+dmheight-y-labelsInterval);
font->draw(
*batch,
string,
pos.x + dmwidth + 2,
pos.y + dmheight - y - labelsInterval,
nullptr,
0
);
}
}

View File

@ -29,6 +29,6 @@ namespace gui {
}
void act(float delta) override;
void draw(const DrawContext* pctx, Assets* assets) override;
void draw(const DrawContext& pctx, const Assets& assets) override;
};
}

View File

@ -5,6 +5,7 @@
#include <algorithm>
#include "Label.hpp"
#include "devtools/syntax_highlighting.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Batch2D.hpp"
#include "graphics/core/Font.hpp"
@ -12,6 +13,7 @@
#include "util/stringutil.hpp"
#include "window/Events.hpp"
#include "window/Window.hpp"
#include "../markdown.hpp"
using namespace gui;
@ -46,10 +48,10 @@ TextBox::TextBox(std::wstring placeholder, glm::vec4 padding)
scrollStep = 0;
}
void TextBox::draw(const DrawContext* pctx, Assets* assets) {
void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
Container::draw(pctx, assets);
font = assets->get<Font>(label->getFontName());
font = assets.get<Font>(label->getFontName());
if (!isFocused()) {
return;
@ -57,39 +59,67 @@ void TextBox::draw(const DrawContext* pctx, Assets* assets) {
glm::vec2 pos = calcPos();
glm::vec2 size = getSize();
auto subctx = pctx->sub();
auto subctx = pctx.sub();
subctx.setScissors(glm::vec4(pos.x, pos.y, size.x, size.y));
const int lineHeight = font->getLineHeight() * label->getLineInterval();
glm::vec2 lcoord = label->calcPos();
lcoord.y -= 2;
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
batch->setColor(glm::vec4(1.0f));
if (editable && int((Window::time() - caretLastMove) * 2) % 2 == 0) {
uint line = label->getLineByTextIndex(caret);
uint lcaret = caret - label->getTextLineOffset(line);
batch->setColor(glm::vec4(1.0f));
int width = font->calcWidth(input, lcaret);
batch->rect(lcoord.x + width, lcoord.y+label->getLineYOffset(line), 2, lineHeight);
batch->rect(
lcoord.x + width,
lcoord.y + label->getLineYOffset(line),
2,
lineHeight
);
}
if (selectionStart != selectionEnd) {
auto selectionCtx = subctx.sub(batch);
selectionCtx.setBlendMode(BlendMode::addition);
uint startLine = label->getLineByTextIndex(selectionStart);
uint endLine = label->getLineByTextIndex(selectionEnd);
batch->setColor(glm::vec4(0.8f, 0.9f, 1.0f, 0.25f));
int start = font->calcWidth(input, selectionStart-label->getTextLineOffset(startLine));
int end = font->calcWidth(input, selectionEnd-label->getTextLineOffset(endLine));
int start = font->calcWidth(
input, selectionStart - label->getTextLineOffset(startLine)
);
int end = font->calcWidth(
input, selectionEnd - label->getTextLineOffset(endLine)
);
int lineY = label->getLineYOffset(startLine);
if (startLine == endLine) {
batch->rect(lcoord.x + start, lcoord.y+lineY, end-start, lineHeight);
batch->rect(
lcoord.x + start, lcoord.y + lineY, end - start, lineHeight
);
} else {
batch->rect(lcoord.x + start, lcoord.y+lineY, label->getSize().x-start-padding.z-padding.x-2, lineHeight);
for (uint i = startLine+1; i < endLine; i++) {
batch->rect(lcoord.x, lcoord.y+label->getLineYOffset(i), label->getSize().x-padding.z-padding.x-2, lineHeight);
batch->rect(
lcoord.x + start,
lcoord.y + lineY,
label->getSize().x - start - padding.z - padding.x - 2,
lineHeight
);
for (uint i = startLine + 1; i < endLine; i++) {
batch->rect(
lcoord.x,
lcoord.y + label->getLineYOffset(i),
label->getSize().x - padding.z - padding.x - 2,
lineHeight
);
}
batch->rect(lcoord.x, lcoord.y+label->getLineYOffset(endLine), end, lineHeight);
batch->rect(
lcoord.x,
lcoord.y + label->getLineYOffset(endLine),
end,
lineHeight
);
}
}
@ -132,13 +162,13 @@ void TextBox::draw(const DrawContext* pctx, Assets* assets) {
}
}
void TextBox::drawBackground(const DrawContext* pctx, Assets*) {
void TextBox::drawBackground(const DrawContext& pctx, const Assets&) {
glm::vec2 pos = calcPos();
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
auto subctx = pctx->sub();
auto subctx = pctx.sub();
subctx.setScissors(glm::vec4(pos.x, pos.y-0.5, size.x, size.y+1));
if (valid) {
@ -162,7 +192,18 @@ void TextBox::drawBackground(const DrawContext* pctx, 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 (markup == "md") {
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()) {
@ -202,7 +243,8 @@ void TextBox::refreshLabel() {
if (multiline && font) {
setScrollable(true);
uint height = label->getLinesNumber() * font->getLineHeight() * label->getLineInterval();
uint height = label->getLinesNumber() * font->getLineHeight() *
label->getLineInterval();
label->setSize(glm::vec2(label->getSize().x, height));
actualLength = height;
} else {
@ -388,7 +430,7 @@ int TextBox::calcIndexAt(int x, int y) const {
return std::min(offset+label->getTextLineOffset(line), input.length());
}
inline std::wstring get_alphabet(wchar_t c) {
static inline std::wstring get_alphabet(wchar_t c) {
std::wstring alphabet {c};
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
return L"abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@ -433,7 +475,11 @@ void TextBox::click(GUI*, int x, int y) {
selectionOrigin = index;
}
void TextBox::mouseMove(GUI*, int x, int y) {
void TextBox::mouseMove(GUI* gui, int x, int y) {
Container::mouseMove(gui, x, y);
if (isScrolling()) {
return;
}
ptrdiff_t index = calcIndexAt(x, y);
setCaret(index);
extendSelection(index);
@ -529,10 +575,21 @@ void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) {
}
}
void TextBox::refreshSyntax() {
if (!syntax.empty()) {
if (auto styles = devtools::syntax_highlight(
syntax, util::wstr2str_utf8(input)
)) {
label->setStyles(std::move(styles));
}
}
}
void TextBox::onInput() {
if (subconsumer) {
subconsumer(input);
}
refreshSyntax();
}
void TextBox::performEditingKeyboardEvents(keycode key) {
@ -632,7 +689,9 @@ size_t TextBox::getLinePos(uint line) const {
return label->getTextLineOffset(line);
}
std::shared_ptr<UINode> TextBox::getAt(glm::vec2 pos, std::shared_ptr<UINode> self) {
std::shared_ptr<UINode> TextBox::getAt(
const glm::vec2& pos, const std::shared_ptr<UINode>& self
) {
return UINode::getAt(pos, self);
}
@ -710,6 +769,7 @@ const std::wstring& TextBox::getText() const {
void TextBox::setText(const std::wstring& value) {
this->input = value;
input.erase(std::remove(input.begin(), input.end(), '\r'), input.end());
refreshSyntax();
}
const std::wstring& TextBox::getPlaceholder() const {
@ -789,3 +849,24 @@ void TextBox::setShowLineNumbers(bool flag) {
bool TextBox::isShowLineNumbers() const {
return showLineNumbers;
}
void TextBox::setSyntax(std::string_view lang) {
syntax = lang;
if (syntax.empty()) {
label->setStyles(nullptr);
} else {
refreshSyntax();
}
}
const std::string& TextBox::getSyntax() const {
return syntax;
}
void TextBox::setMarkup(std::string_view lang) {
markup = lang;
}
const std::string& TextBox::getMarkup() const {
return markup;
}

View File

@ -56,6 +56,8 @@ namespace gui {
bool editable = true;
bool autoresize = false;
bool showLineNumbers = false;
std::string markup;
std::string syntax;
void stepLeft(bool shiftPressed, bool breakSelection);
void stepRight(bool shiftPressed, bool breakSelection);
@ -84,6 +86,8 @@ namespace gui {
void refreshLabel();
void onInput();
void refreshSyntax();
public:
TextBox(
std::wstring placeholder,
@ -212,12 +216,20 @@ namespace gui {
virtual void click(GUI*, int, int) override;
virtual void mouseMove(GUI*, int x, int y) override;
virtual bool isFocuskeeper() const override {return true;}
virtual void draw(const DrawContext* pctx, Assets* assets) override;
virtual void drawBackground(const DrawContext* pctx, Assets* assets) override;
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void drawBackground(const DrawContext& pctx, const Assets& assets) override;
virtual void typed(unsigned int codepoint) override;
virtual void keyPressed(keycode key) override;
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual std::shared_ptr<UINode> getAt(
const glm::vec2& pos, const std::shared_ptr<UINode>& self
) override;
virtual void setOnUpPressed(const runnable &callback);
virtual void setOnDownPressed(const runnable &callback);
virtual void setSyntax(std::string_view lang);
virtual const std::string& getSyntax() const;
virtual void setMarkup(std::string_view lang);
virtual const std::string& getMarkup() const;
};
}

View File

@ -25,12 +25,12 @@ TrackBar::TrackBar(
setHoverColor(glm::vec4(0.01f, 0.02f, 0.03f, 0.5f));
}
void TrackBar::draw(const DrawContext* pctx, Assets*) {
void TrackBar::draw(const DrawContext& pctx, const Assets&) {
if (supplier) {
value = supplier();
}
glm::vec2 pos = calcPos();
auto batch = pctx->getBatch2D();
auto batch = pctx.getBatch2D();
batch->texture(nullptr);
batch->setColor(hover ? hoverColor : color);
batch->rect(pos.x, pos.y, size.x, size.y);

View File

@ -21,7 +21,7 @@ namespace gui {
double value,
double step=1.0,
int trackWidth=12);
virtual void draw(const DrawContext* pctx, Assets* assets) override;
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void setSupplier(doublesupplier);
virtual void setConsumer(doubleconsumer);

View File

@ -111,11 +111,11 @@ bool UINode::isInside(glm::vec2 point) {
point.x < pos.x + size.x && point.y < pos.y + size.y);
}
std::shared_ptr<UINode> UINode::getAt(glm::vec2 point, std::shared_ptr<UINode> self) {
std::shared_ptr<UINode> UINode::getAt(const glm::vec2& point, const std::shared_ptr<UINode>& self) {
if (!isInteractive() || !enabled) {
return nullptr;
}
return isInside(point) ? std::move(self) : nullptr;
return isInside(point) ? self : nullptr;
}
bool UINode::isInteractive() const {

View File

@ -120,7 +120,7 @@ namespace gui {
/// @brief Called every frame for all visible elements
/// @param delta delta timУ
virtual void act(float delta) {};
virtual void draw(const DrawContext* pctx, Assets* assets) = 0;
virtual void draw(const DrawContext& pctx, const Assets& assets) = 0;
virtual void setVisible(bool flag);
bool isVisible() const;
@ -190,7 +190,7 @@ namespace gui {
/// @param pos cursor screen position
/// @param self shared pointer to element
/// @return self, sub-element or nullptr if element is not interractive
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self);
virtual std::shared_ptr<UINode> getAt(const glm::vec2& pos, const std::shared_ptr<UINode>& self);
/// @brief Check if element is opaque for cursor
virtual bool isInteractive() const;

View File

@ -56,8 +56,8 @@ static runnable create_runnable(
const xml::xmlelement& element,
const std::string& name
) {
if (element->has(name)) {
std::string text = element->attr(name).getText();
if (element.has(name)) {
std::string text = element.attr(name).getText();
if (!text.empty()) {
return scripting::create_runnable(
reader.getEnvironment(), text, reader.getFilename()
@ -83,78 +83,78 @@ static onaction create_action(
static void _readUINode(
const UiXmlReader& reader, const xml::xmlelement& element, UINode& node
) {
if (element->has("id")) {
node.setId(element->attr("id").getText());
if (element.has("id")) {
node.setId(element.attr("id").getText());
}
if (element->has("pos")) {
node.setPos(element->attr("pos").asVec2());
if (element.has("pos")) {
node.setPos(element.attr("pos").asVec2());
}
if (element->has("size")) {
node.setSize(element->attr("size").asVec2());
if (element.has("size")) {
node.setSize(element.attr("size").asVec2());
}
if (element->has("color")) {
glm::vec4 color = element->attr("color").asColor();
if (element.has("color")) {
glm::vec4 color = element.attr("color").asColor();
glm::vec4 hoverColor = color;
glm::vec4 pressedColor = color;
if (element->has("hover-color")) {
if (element.has("hover-color")) {
hoverColor = node.getHoverColor();
}
if (element->has("pressed-color")) {
if (element.has("pressed-color")) {
pressedColor = node.getPressedColor();
}
node.setColor(color);
node.setHoverColor(hoverColor);
node.setPressedColor(pressedColor);
}
if (element->has("margin")) {
node.setMargin(element->attr("margin").asVec4());
if (element.has("margin")) {
node.setMargin(element.attr("margin").asVec4());
}
if (element->has("z-index")) {
node.setZIndex(element->attr("z-index").asInt());
if (element.has("z-index")) {
node.setZIndex(element.attr("z-index").asInt());
}
if (element->has("interactive")) {
node.setInteractive(element->attr("interactive").asBool());
if (element.has("interactive")) {
node.setInteractive(element.attr("interactive").asBool());
}
if (element->has("visible")) {
node.setVisible(element->attr("visible").asBool());
if (element.has("visible")) {
node.setVisible(element.attr("visible").asBool());
}
if (element->has("enabled")) {
node.setEnabled(element->attr("enabled").asBool());
if (element.has("enabled")) {
node.setEnabled(element.attr("enabled").asBool());
}
if (element->has("position-func")) {
if (element.has("position-func")) {
node.setPositionFunc(scripting::create_vec2_supplier(
reader.getEnvironment(),
element->attr("position-func").getText(),
element.attr("position-func").getText(),
reader.getFilename()
));
}
if (element->has("size-func")) {
if (element.has("size-func")) {
node.setSizeFunc(scripting::create_vec2_supplier(
reader.getEnvironment(),
element->attr("size-func").getText(),
element.attr("size-func").getText(),
reader.getFilename()
));
}
if (element->has("hover-color")) {
node.setHoverColor(element->attr("hover-color").asColor());
if (element.has("hover-color")) {
node.setHoverColor(element.attr("hover-color").asColor());
}
if (element->has("pressed-color")) {
node.setPressedColor(element->attr("pressed-color").asColor());
if (element.has("pressed-color")) {
node.setPressedColor(element.attr("pressed-color").asColor());
}
std::string alignName = element->attr("align", "").getText();
std::string alignName = element.attr("align", "").getText();
node.setAlign(align_from_string(alignName, node.getAlign()));
if (element->has("gravity")) {
if (element.has("gravity")) {
node.setGravity(gravity_from_string(
element->attr("gravity").getText()
element.attr("gravity").getText()
));
}
if (element->has("tooltip")) {
node.setTooltip(util::str2wstr_utf8(element->attr("tooltip").getText()));
if (element.has("tooltip")) {
node.setTooltip(util::str2wstr_utf8(element.attr("tooltip").getText()));
}
if (element->has("tooltip-delay")) {
node.setTooltipDelay(element->attr("tooltip-delay").asFloat());
if (element.has("tooltip-delay")) {
node.setTooltipDelay(element.attr("tooltip-delay").asFloat());
}
if (auto onclick = create_action(reader, element, "onclick")) {
@ -169,16 +169,16 @@ static void _readUINode(
static void _readContainer(UiXmlReader& reader, const xml::xmlelement& element, Container& container) {
_readUINode(reader, element, container);
if (element->has("scrollable")) {
container.setScrollable(element->attr("scrollable").asBool());
if (element.has("scrollable")) {
container.setScrollable(element.attr("scrollable").asBool());
}
if (element->has("scroll-step")) {
container.setScrollStep(element->attr("scroll-step").asInt());
if (element.has("scroll-step")) {
container.setScrollStep(element.attr("scroll-step").asInt());
}
for (auto& sub : element->getElements()) {
for (auto& sub : element.getElements()) {
if (sub->isText())
continue;
auto subnode = reader.readUINode(sub);
auto subnode = reader.readUINode(*sub);
if (subnode) {
container.add(subnode);
}
@ -189,15 +189,17 @@ void UiXmlReader::readUINode(UiXmlReader& reader, const xml::xmlelement& element
_readContainer(reader, element, container);
}
void UiXmlReader::readUINode(UiXmlReader& reader, const xml::xmlelement& element, UINode& node) {
void UiXmlReader::readUINode(
const UiXmlReader& reader, const xml::xmlelement& element, UINode& node
) {
_readUINode(reader, element, node);
}
static void _readPanel(UiXmlReader& reader, const xml::xmlelement& element, Panel& panel, bool subnodes=true) {
_readUINode(reader, element, panel);
if (element->has("padding")) {
glm::vec4 padding = element->attr("padding").asVec4();
if (element.has("padding")) {
glm::vec4 padding = element.attr("padding").asVec4();
panel.setPadding(padding);
glm::vec2 size = panel.getSize();
panel.setSize(glm::vec2(
@ -205,23 +207,23 @@ static void _readPanel(UiXmlReader& reader, const xml::xmlelement& element, Pane
size.y + padding.y + padding.w
));
}
if (element->has("size")) {
if (element.has("size")) {
panel.setResizing(false);
}
if (element->has("max-length")) {
panel.setMaxLength(element->attr("max-length").asInt());
if (element.has("max-length")) {
panel.setMaxLength(element.attr("max-length").asInt());
}
if (element->has("orientation")) {
auto &oname = element->attr("orientation").getText();
if (element.has("orientation")) {
auto &oname = element.attr("orientation").getText();
if (oname == "horizontal") {
panel.setOrientation(Orientation::horizontal);
}
}
if (subnodes) {
for (auto& sub : element->getElements()) {
for (auto& sub : element.getElements()) {
if (sub->isText())
continue;
auto subnode = reader.readUINode(sub);
auto subnode = reader.readUINode(*sub);
if (subnode) {
panel.add(subnode);
}
@ -231,8 +233,8 @@ static void _readPanel(UiXmlReader& reader, const xml::xmlelement& element, Pane
static std::wstring readAndProcessInnerText(const xml::xmlelement& element, const std::string& context) {
std::wstring text = L"";
if (element->size() == 1) {
std::string source = element->sub(0)->attr("#").getText();
if (element.size() == 1) {
std::string source = element.sub(0).attr("#").getText();
util::trim(source);
text = util::str2wstr_utf8(source);
if (text[0] == '@') {
@ -246,33 +248,38 @@ static std::wstring readAndProcessInnerText(const xml::xmlelement& element, cons
return text;
}
static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, const xml::xmlelement& element) {
static std::shared_ptr<UINode> readLabel(
const UiXmlReader& reader, const xml::xmlelement& element
) {
std::wstring text = readAndProcessInnerText(element, reader.getContext());
auto label = std::make_shared<Label>(text);
_readUINode(reader, element, *label);
if (element->has("valign")) {
if (element.has("valign")) {
label->setVerticalAlign(
align_from_string(element->attr("valign").getText(), label->getVerticalAlign())
align_from_string(element.attr("valign").getText(), label->getVerticalAlign())
);
}
if (element->has("supplier")) {
if (element.has("supplier")) {
label->textSupplier(scripting::create_wstring_supplier(
reader.getEnvironment(),
element->attr("supplier").getText(),
element.attr("supplier").getText(),
reader.getFilename()
));
}
if (element->has("autoresize")) {
label->setAutoResize(element->attr("autoresize").asBool());
if (element.has("autoresize")) {
label->setAutoResize(element.attr("autoresize").asBool());
}
if (element->has("multiline")) {
label->setMultiline(element->attr("multiline").asBool());
if (!element->has("valign")) {
if (element.has("multiline")) {
label->setMultiline(element.attr("multiline").asBool());
if (!element.has("valign")) {
label->setVerticalAlign(Align::top);
}
}
if (element->has("text-wrap")) {
label->setTextWrapping(element->attr("text-wrap").asBool());
if (element.has("text-wrap")) {
label->setTextWrapping(element.attr("text-wrap").asBool());
}
if (element.has("markup")) {
label->setMarkup(element.attr("markup").getText());
}
return label;
}
@ -284,19 +291,19 @@ static std::shared_ptr<UINode> readContainer(UiXmlReader& reader, const xml::xml
}
static std::shared_ptr<UINode> readPanel(UiXmlReader& reader, const xml::xmlelement& element) {
float interval = element->attr("interval", "2").asFloat();
float interval = element.attr("interval", "2").asFloat();
auto panel = std::make_shared<Panel>(glm::vec2(), glm::vec4(), interval);
_readPanel(reader, element, *panel);
return panel;
}
static std::shared_ptr<UINode> readButton(UiXmlReader& reader, const xml::xmlelement& element) {
glm::vec4 padding = element->attr("padding", "10").asVec4();
glm::vec4 padding = element.attr("padding", "10").asVec4();
std::shared_ptr<Button> button;
auto& elements = element->getElements();
auto& elements = element.getElements();
if (!elements.empty() && elements[0]->getTag() != "#") {
auto inner = reader.readUINode(element->getElements().at(0));
auto inner = reader.readUINode(*elements.at(0));
if (inner != nullptr) {
button = std::make_shared<Button>(inner, padding);
} else {
@ -308,30 +315,32 @@ static std::shared_ptr<UINode> readButton(UiXmlReader& reader, const xml::xmlele
button = std::make_shared<Button>(text, padding, nullptr);
_readPanel(reader, element, *button, true);
}
if (element->has("text-align")) {
button->setTextAlign(align_from_string(element->attr("text-align").getText(), button->getTextAlign()));
if (element.has("text-align")) {
button->setTextAlign(align_from_string(
element.attr("text-align").getText(), button->getTextAlign()
));
}
return button;
}
static std::shared_ptr<UINode> readCheckBox(UiXmlReader& reader, const xml::xmlelement& element) {
auto text = readAndProcessInnerText(element, reader.getContext());
bool checked = element->attr("checked", "false").asBool();
bool checked = element.attr("checked", "false").asBool();
auto checkbox = std::make_shared<FullCheckBox>(text, glm::vec2(32), checked);
_readPanel(reader, element, *checkbox);
if (element->has("consumer")) {
if (element.has("consumer")) {
checkbox->setConsumer(scripting::create_bool_consumer(
reader.getEnvironment(),
element->attr("consumer").getText(),
element.attr("consumer").getText(),
reader.getFilename()
));
}
if (element->has("supplier")) {
if (element.has("supplier")) {
checkbox->setSupplier(scripting::create_bool_supplier(
reader.getEnvironment(),
element->attr("supplier").getText(),
element.attr("supplier").getText(),
reader.getFilename()
));
}
@ -339,15 +348,15 @@ static std::shared_ptr<UINode> readCheckBox(UiXmlReader& reader, const xml::xmle
}
static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlelement& element) {
auto placeholder = util::str2wstr_utf8(element->attr("placeholder", "").getText());
auto hint = util::str2wstr_utf8(element->attr("hint", "").getText());
auto placeholder = util::str2wstr_utf8(element.attr("placeholder", "").getText());
auto hint = util::str2wstr_utf8(element.attr("hint", "").getText());
auto text = readAndProcessInnerText(element, reader.getContext());
auto textbox = std::make_shared<TextBox>(placeholder, glm::vec4(0.0f));
textbox->setHint(hint);
_readContainer(reader, element, *textbox);
if (element->has("padding")) {
glm::vec4 padding = element->attr("padding").asVec4();
if (element.has("padding")) {
glm::vec4 padding = element.attr("padding").asVec4();
textbox->setPadding(padding);
glm::vec2 size = textbox->getSize();
textbox->setSize(glm::vec2(
@ -357,55 +366,61 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
}
textbox->setText(text);
if (element->has("multiline")) {
textbox->setMultiline(element->attr("multiline").asBool());
if (element.has("syntax")) {
textbox->setSyntax(element.attr("syntax").getText());
}
if (element->has("text-wrap")) {
textbox->setTextWrapping(element->attr("text-wrap").asBool());
if (element.has("multiline")) {
textbox->setMultiline(element.attr("multiline").asBool());
}
if (element->has("editable")) {
textbox->setEditable(element->attr("editable").asBool());
if (element.has("text-wrap")) {
textbox->setTextWrapping(element.attr("text-wrap").asBool());
}
if (element->has("autoresize")) {
textbox->setAutoResize(element->attr("autoresize").asBool());
if (element.has("editable")) {
textbox->setEditable(element.attr("editable").asBool());
}
if (element->has("line-numbers")) {
textbox->setShowLineNumbers(element->attr("line-numbers").asBool());
if (element.has("autoresize")) {
textbox->setAutoResize(element.attr("autoresize").asBool());
}
if (element->has("consumer")) {
if (element.has("line-numbers")) {
textbox->setShowLineNumbers(element.attr("line-numbers").asBool());
}
if (element.has("markup")) {
textbox->setMarkup(element.attr("markup").getText());
}
if (element.has("consumer")) {
textbox->setTextConsumer(scripting::create_wstring_consumer(
reader.getEnvironment(),
element->attr("consumer").getText(),
element.attr("consumer").getText(),
reader.getFilename()
));
}
if (element->has("sub-consumer")) {
if (element.has("sub-consumer")) {
textbox->setTextSubConsumer(scripting::create_wstring_consumer(
reader.getEnvironment(),
element->attr("sub-consumer").getText(),
element.attr("sub-consumer").getText(),
reader.getFilename()
));
}
if (element->has("supplier")) {
if (element.has("supplier")) {
textbox->setTextSupplier(scripting::create_wstring_supplier(
reader.getEnvironment(),
element->attr("supplier").getText(),
element.attr("supplier").getText(),
reader.getFilename()
));
}
if (element->has("focused-color")) {
textbox->setFocusedColor(element->attr("focused-color").asColor());
if (element.has("focused-color")) {
textbox->setFocusedColor(element.attr("focused-color").asColor());
}
if (element->has("error-color")) {
textbox->setErrorColor(element->attr("error-color").asColor());
if (element.has("error-color")) {
textbox->setErrorColor(element.attr("error-color").asColor());
}
if (element->has("text-color")) {
textbox->setTextColor(element->attr("text-color").asColor());
if (element.has("text-color")) {
textbox->setTextColor(element.attr("text-color").asColor());
}
if (element->has("validator")) {
if (element.has("validator")) {
textbox->setTextValidator(scripting::create_wstring_validator(
reader.getEnvironment(),
element->attr("validator").getText(),
element.attr("validator").getText(),
reader.getFilename()
));
}
@ -418,61 +433,70 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
return textbox;
}
static std::shared_ptr<UINode> readImage(UiXmlReader& reader, const xml::xmlelement& element) {
std::string src = element->attr("src", "").getText();
static std::shared_ptr<UINode> readImage(
const UiXmlReader& reader, const xml::xmlelement& element
) {
std::string src = element.attr("src", "").getText();
auto image = std::make_shared<Image>(src);
_readUINode(reader, element, *image);
return image;
}
static std::shared_ptr<UINode> readTrackBar(UiXmlReader& reader, const xml::xmlelement& element) {
static std::shared_ptr<UINode> readTrackBar(
const UiXmlReader& reader, const xml::xmlelement& element
) {
const auto& env = reader.getEnvironment();
const auto& file = reader.getFilename();
float minv = element->attr("min", "0.0").asFloat();
float maxv = element->attr("max", "1.0").asFloat();
float def = element->attr("value", "0.0").asFloat();
float step = element->attr("step", "1.0").asFloat();
int trackWidth = element->attr("track-width", "12").asInt();
float minv = element.attr("min", "0.0").asFloat();
float maxv = element.attr("max", "1.0").asFloat();
float def = element.attr("value", "0.0").asFloat();
float step = element.attr("step", "1.0").asFloat();
int trackWidth = element.attr("track-width", "12").asInt();
auto bar = std::make_shared<TrackBar>(minv, maxv, def, step, trackWidth);
_readUINode(reader, element, *bar);
if (element->has("consumer")) {
if (element.has("consumer")) {
bar->setConsumer(scripting::create_number_consumer(
env, element->attr("consumer").getText(), file));
env, element.attr("consumer").getText(), file));
}
if (element->has("sub-consumer")) {
if (element.has("sub-consumer")) {
bar->setSubConsumer(scripting::create_number_consumer(
env, element->attr("sub-consumer").getText(), file));
env, element.attr("sub-consumer").getText(), file));
}
if (element->has("supplier")) {
if (element.has("supplier")) {
bar->setSupplier(scripting::create_number_supplier(
env, element->attr("supplier").getText(), file));
env, element.attr("supplier").getText(), file));
}
if (element->has("track-color")) {
bar->setTrackColor(element->attr("track-color").asColor());
if (element.has("track-color")) {
bar->setTrackColor(element.attr("track-color").asColor());
}
if (element->has("change-on-release")) {
bar->setChangeOnRelease(element->attr("change-on-release").asBool());
if (element.has("change-on-release")) {
bar->setChangeOnRelease(element.attr("change-on-release").asBool());
}
return bar;
}
static std::shared_ptr<UINode> readInputBindBox(UiXmlReader& reader, const xml::xmlelement& element) {
auto bindname = element->attr("binding").getText();
auto bindname = element.attr("binding").getText();
auto found = Events::bindings.find(bindname);
if (found == Events::bindings.end()) {
throw std::runtime_error("binding does not exists "+util::quote(bindname));
}
glm::vec4 padding = element->attr("padding", "6").asVec4();
glm::vec4 padding = element.attr("padding", "6").asVec4();
auto bindbox = std::make_shared<InputBindBox>(found->second, padding);
_readPanel(reader, element, *bindbox);
return bindbox;
}
static slotcallback readSlotFunc(InventoryView* view, UiXmlReader& reader, xml::xmlelement& element, const std::string& attr) {
static slotcallback readSlotFunc(
InventoryView* view,
const UiXmlReader& reader,
const xml::xmlelement& element,
const std::string& attr
) {
auto consumer = scripting::create_int_array_consumer(
reader.getEnvironment(),
element->attr(attr).getText()
element.attr(attr).getText()
);
return [=](uint slot, ItemStack&) {
int args[] {int(view->getInventory()->getId()), int(slot)};
@ -480,22 +504,24 @@ static slotcallback readSlotFunc(InventoryView* view, UiXmlReader& reader, xml::
};
}
static void readSlot(InventoryView* view, UiXmlReader& reader, xml::xmlelement element) {
int index = element->attr("index", "0").asInt();
bool itemSource = element->attr("item-source", "false").asBool();
bool taking = element->attr("taking", "true").asBool();
bool placing = element->attr("placing", "true").asBool();
static void readSlot(
InventoryView* view, UiXmlReader& reader, const xml::xmlelement& element
) {
int index = element.attr("index", "0").asInt();
bool itemSource = element.attr("item-source", "false").asBool();
bool taking = element.attr("taking", "true").asBool();
bool placing = element.attr("placing", "true").asBool();
SlotLayout layout(index, glm::vec2(), true, itemSource, nullptr, nullptr, nullptr);
if (element->has("pos")) {
layout.position = element->attr("pos").asVec2();
if (element.has("pos")) {
layout.position = element.attr("pos").asVec2();
}
if (element->has("updatefunc")) {
if (element.has("updatefunc")) {
layout.updateFunc = readSlotFunc(view, reader, element, "updatefunc");
}
if (element->has("sharefunc")) {
if (element.has("sharefunc")) {
layout.shareFunc = readSlotFunc(view, reader, element, "sharefunc");
}
if (element->has("onrightclick")) {
if (element.has("onrightclick")) {
layout.rightClick = readSlotFunc(view, reader, element, "onrightclick");
}
layout.taking = taking;
@ -505,19 +531,21 @@ static void readSlot(InventoryView* view, UiXmlReader& reader, xml::xmlelement e
view->add(slot);
}
static void readSlotsGrid(InventoryView* view, UiXmlReader& reader, xml::xmlelement element) {
int startIndex = element->attr("start-index", "0").asInt();
int rows = element->attr("rows", "0").asInt();
int cols = element->attr("cols", "0").asInt();
int count = element->attr("count", "0").asInt();
static void readSlotsGrid(
InventoryView* view, const UiXmlReader& reader, const xml::xmlelement& element
) {
int startIndex = element.attr("start-index", "0").asInt();
int rows = element.attr("rows", "0").asInt();
int cols = element.attr("cols", "0").asInt();
int count = element.attr("count", "0").asInt();
const int slotSize = InventoryView::SLOT_SIZE;
bool taking = element->attr("taking", "true").asBool();
bool placing = element->attr("placing", "true").asBool();
int interval = element->attr("interval", "-1").asInt();
bool taking = element.attr("taking", "true").asBool();
bool placing = element.attr("placing", "true").asBool();
int interval = element.attr("interval", "-1").asInt();
if (interval < 0) {
interval = InventoryView::SLOT_INTERVAL;
}
int padding = element->attr("padding", "-1").asInt();
int padding = element.attr("padding", "-1").asInt();
if (padding < 0) {
padding = interval;
}
@ -528,18 +556,18 @@ static void readSlotsGrid(InventoryView* view, UiXmlReader& reader, xml::xmlelem
} else if (count == 0) {
count = rows * cols;
}
bool itemSource = element->attr("item-source", "false").asBool();
bool itemSource = element.attr("item-source", "false").asBool();
SlotLayout layout(-1, glm::vec2(), true, itemSource, nullptr, nullptr, nullptr);
if (element->has("pos")) {
layout.position = element->attr("pos").asVec2();
if (element.has("pos")) {
layout.position = element.attr("pos").asVec2();
}
if (element->has("updatefunc")) {
if (element.has("updatefunc")) {
layout.updateFunc = readSlotFunc(view, reader, element, "updatefunc");
}
if (element->has("sharefunc")) {
if (element.has("sharefunc")) {
layout.shareFunc = readSlotFunc(view, reader, element, "sharefunc");
}
if (element->has("onrightclick")) {
if (element.has("onrightclick")) {
layout.rightClick = readSlotFunc(view, reader, element, "onrightclick");
}
layout.padding = padding;
@ -571,11 +599,11 @@ static std::shared_ptr<UINode> readInventory(UiXmlReader& reader, const xml::xml
reader.addIgnore("slots-grid");
reader.readUINode(reader, element, *view);
for (auto& sub : element->getElements()) {
for (auto& sub : element.getElements()) {
if (sub->getTag() == "slot") {
readSlot(view.get(), reader, sub);
readSlot(view.get(), reader, *sub);
} else if (sub->getTag() == "slots-grid") {
readSlotsGrid(view.get(), reader, sub);
readSlotsGrid(view.get(), reader, *sub);
}
}
return view;
@ -618,18 +646,18 @@ void UiXmlReader::addIgnore(const std::string& tag) {
}
std::shared_ptr<UINode> UiXmlReader::readUINode(const xml::xmlelement& element) {
if (element->has("if")) {
const auto& cond = element->attr("if").getText();
if (element.has("if")) {
const auto& cond = element.attr("if").getText();
if (cond.empty() || cond == "false" || cond == "nil")
return nullptr;
}
if (element->has("ifnot")) {
const auto& cond = element->attr("ifnot").getText();
if (element.has("ifnot")) {
const auto& cond = element.attr("ifnot").getText();
if (!(cond.empty() || cond == "false" || cond == "nil"))
return nullptr;
}
const std::string& tag = element->getTag();
const std::string& tag = element.getTag();
auto found = readers.find(tag);
if (found == readers.end()) {
if (ignored.find(tag) != ignored.end()) {
@ -638,9 +666,9 @@ std::shared_ptr<UINode> UiXmlReader::readUINode(const xml::xmlelement& element)
throw std::runtime_error("unsupported element '"+tag+"'");
}
bool hascontext = element->has("context");
bool hascontext = element.has("context");
if (hascontext) {
contextStack.push(element->attr("context").getText());
contextStack.push(element.attr("context").getText());
}
auto node = found->second(*this, element);
if (hascontext) {
@ -655,8 +683,7 @@ std::shared_ptr<UINode> UiXmlReader::readXML(
) {
this->filename = filename;
auto document = xml::parse(filename, source);
auto root = document->getRoot();
return readUINode(root);
return readUINode(*document->getRoot());
}
std::shared_ptr<UINode> UiXmlReader::readXML(

View File

@ -11,7 +11,8 @@
namespace gui {
class UiXmlReader;
using uinode_reader = std::function<std::shared_ptr<UINode>(UiXmlReader&, xml::xmlelement)>;
using uinode_reader = std::function<
std::shared_ptr<UINode>(UiXmlReader&, const xml::xmlelement&)>;
class UiXmlReader {
std::unordered_map<std::string, uinode_reader> readers;
@ -29,7 +30,7 @@ namespace gui {
std::shared_ptr<UINode> readUINode(const xml::xmlelement& element);
void readUINode(
UiXmlReader& reader,
const UiXmlReader& reader,
const xml::xmlelement& element,
UINode& node
);

View File

@ -0,0 +1,111 @@
#include "markdown.hpp"
#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;
}
pos--;
}
} 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;
}
if (first == '\n') {
styles.palette.push_back(styles.palette.at(1));
}
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);
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <memory>
#include <sstream>
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);
template <typename CharT>
inline std::basic_string<CharT> escape(std::string_view source) {
std::basic_stringstream<CharT> ss;
int pos = 0;
while (pos < source.size()) {
CharT first = source[pos];
if (first == '\\' && pos + 1 < source.size()) {
CharT second = source[++pos];
ss << first << second;
pos++;
continue;
} else if (first == '*' || first == '~' || first == '_') {
ss << '\\';
}
ss << first;
pos++;
}
return ss.str();
}
}

View File

@ -5,7 +5,7 @@
Inventory::Inventory(int64_t id, size_t size) : id(id), slots(size) {
}
Inventory::Inventory(const Inventory& orig) {
Inventory::Inventory(const Inventory& orig) : id(0) {
this->slots = orig.slots;
}

View File

@ -31,9 +31,16 @@ static int l_add_command(lua::State* L) {
static int l_execute(lua::State* L) {
auto prompt = lua::require_string(L, 1);
auto result = engine->getCommandsInterpreter()->execute(prompt);
lua::pushvalue(L, result);
return 1;
try {
auto result = engine->getCommandsInterpreter()->execute(prompt);
lua::pushvalue(L, result);
return 1;
} catch (const parsing_error& err) {
if (std::string(err.what()).find("unknown command ") == 0) {
throw;
}
throw std::runtime_error(err.errorLog());
}
}
static int l_set(lua::State* L) {

View File

@ -12,6 +12,8 @@
#include "graphics/ui/elements/TrackBar.hpp"
#include "graphics/ui/elements/UINode.hpp"
#include "graphics/ui/gui_util.hpp"
#include "graphics/ui/markdown.hpp"
#include "graphics/core/Font.hpp"
#include "items/Inventories.hpp"
#include "util/stringutil.hpp"
#include "world/Level.hpp"
@ -299,6 +301,22 @@ static int p_get_line_numbers(UINode* node, lua::State* L) {
return 0;
}
static int p_get_syntax(UINode* node, lua::State* L) {
if (auto box = dynamic_cast<TextBox*>(node)) {
return lua::pushstring(L, box->getSyntax());
}
return 0;
}
static int p_get_markup(UINode* node, lua::State* L) {
if (auto box = dynamic_cast<TextBox*>(node)) {
return lua::pushstring(L, box->getMarkup());
} else if (auto label = dynamic_cast<Label*>(node)) {
return lua::pushstring(L, label->getMarkup());
}
return 0;
}
static int p_get_src(UINode* node, lua::State* L) {
if (auto image = dynamic_cast<Image*>(node)) {
return lua::pushstring(L, image->getTexture());
@ -420,6 +438,8 @@ static int l_gui_getattr(lua::State* L) {
{"lineNumbers", p_get_line_numbers},
{"lineAt", p_get_line_at},
{"linePos", p_get_line_pos},
{"syntax", p_get_syntax},
{"markup", p_get_markup},
{"src", p_get_src},
{"value", p_get_value},
{"min", p_get_min},
@ -512,6 +532,18 @@ static void p_set_line_numbers(UINode* node, lua::State* L, int idx) {
box->setShowLineNumbers(lua::toboolean(L, idx));
}
}
static void p_set_syntax(UINode* node, lua::State* L, int idx) {
if (auto box = dynamic_cast<TextBox*>(node)) {
box->setSyntax(lua::require_string(L, idx));
}
}
static void p_set_markup(UINode* node, lua::State* L, int idx) {
if (auto box = dynamic_cast<TextBox*>(node)) {
box->setMarkup(lua::require_string(L, idx));
} else if (auto label = dynamic_cast<Label*>(node)) {
label->setMarkup(lua::require_string(L, idx));
}
}
static void p_set_src(UINode* node, lua::State* L, int idx) {
if (auto image = dynamic_cast<Image*>(node)) {
image->setTexture(lua::require_string(L, idx));
@ -612,6 +644,8 @@ static int l_gui_setattr(lua::State* L) {
{"text", p_set_text},
{"editable", p_set_editable},
{"lineNumbers", p_set_line_numbers},
{"syntax", p_set_syntax},
{"markup", p_set_markup},
{"src", p_set_src},
{"caret", p_set_caret},
{"value", p_set_value},
@ -694,6 +728,25 @@ static int l_gui_getviewport(lua::State* L) {
return lua::pushvec2(L, engine->getGUI()->getContainer()->getSize());
}
static int l_gui_clear_markup(lua::State* L) {
auto lang = lua::require_string(L, 1);
std::string text = lua::require_string(L, 2);
if (std::strcmp(lang, "md") == 0) {
auto [processed, _] = markdown::process(text, true);
text = std::move(processed);
}
return lua::pushstring(L, text);
}
static int l_gui_escape_markup(lua::State* L) {
auto lang = lua::require_string(L, 1);
std::string text = lua::require_string(L, 2);
if (std::strcmp(lang, "md") == 0) {
text = std::move(markdown::escape<char>(text));
}
return lua::pushstring(L, text);
}
const luaL_Reg guilib[] = {
{"get_viewport", lua::wrap<l_gui_getviewport>},
{"getattr", lua::wrap<l_gui_getattr>},
@ -701,5 +754,8 @@ const luaL_Reg guilib[] = {
{"get_env", lua::wrap<l_gui_get_env>},
{"str", lua::wrap<l_gui_str>},
{"get_locales_info", lua::wrap<l_gui_get_locales_info>},
{"clear_markup", lua::wrap<l_gui_clear_markup>},
{"escape_markup", lua::wrap<l_gui_escape_markup>},
{"__reindex", lua::wrap<l_gui_reindex>},
{NULL, NULL}};
{NULL, NULL}
};

View File

@ -53,7 +53,7 @@ struct ParticlesPreset : public Serializable {
/// @brief Size of random sub-uv region
float randomSubUV = 1.0f;
/// @brief Animation frames
std::vector<std::string> frames {};
std::vector<std::string> frames;
dv::value serialize() const override;
void deserialize(const dv::value& src) override;

View File

@ -0,0 +1,20 @@
#include <gtest/gtest.h>
#include "coders/commons.hpp"
#include "coders/lua_parsing.hpp"
#include "files/files.hpp"
#include "util/stringutil.hpp"
TEST(lua_parsing, Tokenizer) {
auto filename = "../../res/scripts/stdlib.lua";
auto source = files::read_string(std::filesystem::u8path(filename));
try {
auto tokens = lua::tokenize(filename, source);
for (const auto& token : tokens) {
std::cout << (int)token.tag << " " << util::quote(token.text) << std::endl;
}
} catch (const parsing_error& err) {
std::cerr << err.errorLog() << std::endl;
throw err;
}
}