Zepha/src/client/gui/TextElement.cpp

247 lines
6.8 KiB
C++

#include <functional>
#include "TextElement.h"
#include "client/gui/Root.h"
#include "client/graph/Model.h"
#include "client/graph/mesh/EntityMesh.h"
void Gui::TextElement::updateElement() {
const string text = getStyle<string>(Prop::CONTENT, "");
if (!font) font = std::make_unique<Font>(root.atlas, root.atlas["font"]);
usize newHash = std::hash<string>{}(text);
if (hash != newHash) {
hash = newHash;
vec4 textColor = getStyle<vec4, Type::COLOR>(Prop::TEXT_COLOR, vec4(1));
vec4 backgroundColor = getStyle<vec4, Type::COLOR>(Prop::BACKGROUND, vec4(0));
u32 ind = 0;
u32 width = 0;
vec<EntityVertex> vertices;
vertices.reserve(text.length() * 8 + 200);
vec<u32> indices;
indices.reserve(text.length() * 12 + 240);
vec<string> lines;
{
std::stringstream textStream(text);
string line;
while (std::getline(textStream, line, '\n')) lines.emplace_back(line);
}
vec3 offset = {};
u32 h = Font::C_HEIGHT;
bool bold = false;
bool italic = false;
i32 underline = -1;
i32 strikethrough = -1;
u32 strikethroughVertStart = 0;
vec4 color = textColor;
for (usize i = 0; i < lines.size(); i++) {
let& line = lines[i];
bool empty = line.find_first_not_of(" \t\n") == -1;
if (empty) {
offset.x = 0;
offset.y += h / 2;
continue;
}
u32 bgVertStart = 0;
if (backgroundColor.w != 0) {
bgVertStart = vertices.size();
for (u32 i = 0; i < 4; i++) vertices.push_back({});
for (u32 i : INDICES) indices.push_back(i + ind);
ind += 4;
}
for (usize j = 0; j < line.length() + 1; j++) {
char c = j < line.length() ? line[j] : ' ';
if (c == '\t') c = ' ';
if (c == '`') {
bool flushDecorators = j == line.length();
char d = line[++j];
if (d == '`') goto escape_formatting;
else if (d == ' ') offset.x++;
else if (d == 'b') bold = true;
else if (d == 'i') italic = true;
else if (d == 'u') underline = offset.x;
else if (d == 's') {
strikethrough = offset.x;
strikethroughVertStart = vertices.size();
for (u32 i = 0; i < 4; i++) vertices.push_back({});
for (u32 i : INDICES) indices.push_back(i + ind);
ind += 4;
}
else if (d == 'c') flushDecorators = true;
else if (d == 'r') {
bold = false;
italic = false;
flushDecorators = true;
}
if (flushDecorators) {
if (underline != -1) {
TextElement::drawRect(
{ underline, offset.y + h - 1, offset.x, offset.y + h },
color, vertices, indices, ind);
TextElement::drawRect(
{ underline + 1, offset.y + h, offset.x + 1, offset.y + h + 1 },
color * BG_MULTIPLE, vertices, indices, ind);
underline = offset.x;
}
if (strikethrough != -1) {
TextElement::drawRect(
{ strikethrough, offset.y + h / 2, offset.x, offset.y + h / 2 + 1 },
color, vertices, indices, ind);
TextElement::drawRect(
{ strikethrough + 1, offset.y + h / 2 + 1, offset.x + 1, offset.y + h / 2 + 2 },
color * BG_MULTIPLE, vertices, indices, ind, strikethroughVertStart);
strikethrough = offset.x;
}
if (d == 'r') {
color = textColor;
underline = -1;
strikethrough = -1;
}
}
if (d == 'c') {
char code = line[++j];
if (code == 'r') color = textColor;
else {
u32 v;
std::stringstream ss;
ss << std::hex << code;
ss >> v;
color = COLORS[v];
}
}
continue;
}
escape_formatting:
if (j == line.length()) continue;
u32 w = font->getCharWidth(c) + 1;
vec4 UV = font->getCharUVs(c);
for (u32 k = 0; k < (bold ? 4 : 2); k++) {
vec4 c = color;
if (k == 0 || (k == 1 && bold)) c *= BG_MULTIPLE;
if (k == 0) {
offset.x += 1;
offset.y += 1;
}
else if ((k == 1 || k == 3) && bold) {
offset.x += 1;
}
else if ((k == 1 && !bold) || (k == 2 && bold)) {
offset.x -= bold ? 2 : 1;
offset.y -= 1;
}
vertices.emplace_back(offset + vec3(italic ? 2 : 0, 0, 0), vec4 { UV.x, UV.y, 0, c.w },
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
vertices.emplace_back(offset + vec3(0, h, 0), vec4 { UV.x, UV.w, 0, c.w },
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
vertices.emplace_back(offset + vec3(w, h, 0), vec4 { UV.z, UV.w, 0, c.w },
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
vertices.emplace_back(offset + vec3(w + (italic ? 2 : 0), 0, 0), vec4 { UV.z, UV.y, 0, c.w },
vec3(c), 1.f, vec3 {}, ivec4 {}, vec4 {});
for (u32 i : INDICES) indices.push_back(i + ind);
ind += 4;
}
offset.x += w;
}
if (backgroundColor.w != 0) TextElement::drawRect({ -1, offset.y - 1, offset.x + 2, offset.y + h + 1 },
backgroundColor, vertices, indices, ind, bgVertStart);
if (offset.x > width) width = offset.x;
offset.x = 0;
offset.y += h + 2;
}
let mesh = make_unique<EntityMesh>();
mesh->create(vertices, indices);
let model = make_shared<Model>();
model->fromMesh(std::move(mesh));
entity.setModel(model);
}
let scale = getStyle<f32, Type::LENGTH>(Prop::TEXT_SIZE, 3.f);
let margin = getStyle<vec4, Type::LENGTH>(Prop::MARGIN, {});
entity.setScale(ivec3(scale, scale, 0));
entity.setPos(ivec3(getComputedScreenPos() + vec2 { margin.x, margin.y }, 0));
Element::updateElement();
}
void Gui::TextElement::drawRect(const vec4 pos, const vec4 color,
vec<EntityVertex>& vertices, vec<u32>& indices, u32& ind, const u32 insert) {
vec<EntityVertex> myVerts = {
{ vec3 { pos.x, pos.y, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} },
{ vec3 { pos.x, pos.w, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} },
{ vec3 { pos.z, pos.w, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} },
{ vec3 { pos.z, pos.y, 0 }, color, vec3(1), 0.f, vec3 {}, ivec4 {}, vec4 {} }
};
if (insert != -1) {
vertices[insert] = myVerts[0];
vertices[insert + 1] = myVerts[1];
vertices[insert + 2] = myVerts[2];
vertices[insert + 3] = myVerts[3];
}
else {
for (EntityVertex& vert : myVerts) vertices.emplace_back(vert);
for (u32 i : INDICES) indices.push_back(i + ind);
ind += 4;
}
}
const array<vec4, 16> Gui::TextElement::COLORS = {
Util::hexToColorVec("#ffffff"),
Util::hexToColorVec("#aaaaaa"),
Util::hexToColorVec("#666666"),
Util::hexToColorVec("#000000"),
Util::hexToColorVec("#f53658"),
Util::hexToColorVec("#ff9940"),
Util::hexToColorVec("#fffb82"),
Util::hexToColorVec("#9fff80"),
Util::hexToColorVec("#0fa84f"),
Util::hexToColorVec("#26d4d4"),
Util::hexToColorVec("#7df4ff"),
Util::hexToColorVec("#33a2f5"),
Util::hexToColorVec("#2c58e8"),
Util::hexToColorVec("#b05cff"),
Util::hexToColorVec("#fd7dff"),
Util::hexToColorVec("#ff739f"),
};
const array<u32, 6> Gui::TextElement::INDICES = { 0, 1, 2, 2, 3, 0 };
const vec4 Gui::TextElement::BG_MULTIPLE = { 0.3, 0.3, 0.35, 0.75 };