summaryrefslogtreecommitdiff
path: root/Client/Source/GUI
diff options
context:
space:
mode:
Diffstat (limited to 'Client/Source/GUI')
-rw-r--r--Client/Source/GUI/Font.cpp302
-rw-r--r--Client/Source/GUI/Font.h149
-rw-r--r--Client/Source/GUI/TextMeshGenerator.cpp89
-rw-r--r--Client/Source/GUI/TextMeshGenerator.h31
-rw-r--r--Client/Source/GUI/UI9Slicing.cpp112
-rw-r--r--Client/Source/GUI/UI9Slicing.h25
-rw-r--r--Client/Source/GUI/UILine.cpp48
-rw-r--r--Client/Source/GUI/UILine.h21
-rw-r--r--Client/Source/GUI/UIMesh.cpp15
-rw-r--r--Client/Source/GUI/UIMesh.h42
-rw-r--r--Client/Source/GUI/UIQuad.cpp64
-rw-r--r--Client/Source/GUI/UIQuad.h21
-rw-r--r--Client/Source/GUI/UISquare.cpp53
-rw-r--r--Client/Source/GUI/UISquare.h22
-rw-r--r--Client/Source/GUI/UITextMesh.cpp278
-rw-r--r--Client/Source/GUI/UITextMesh.h69
-rw-r--r--Client/Source/GUI/freetype.h4
-rw-r--r--Client/Source/GUI/utf8.cpp415
-rw-r--r--Client/Source/GUI/utf8.h113
19 files changed, 1873 insertions, 0 deletions
diff --git a/Client/Source/GUI/Font.cpp b/Client/Source/GUI/Font.cpp
new file mode 100644
index 0000000..1efe551
--- /dev/null
+++ b/Client/Source/GUI/Font.cpp
@@ -0,0 +1,302 @@
+#include "freetype.h"
+#include "Font.h"
+
+#include "../Math/Functions.h"
+#include "../Math/Math.h"
+#include "../Debug/Log.h"
+#include "../Utilities/Assert.h"
+#include "../Graphics/ImageData.h"
+
+using namespace character;
+
+/*
+* http://madig.github.io/freetype-web/documentation/tutorial/
+* https://unicode-table.com/en/#cjk-unified-ideographs-extension-a
+* https://learnopengl.com/In-Practice/Text-Rendering
+* https://www.zhihu.com/question/294660079
+* https://baike.baidu.com/item/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E6%96%87%E7%A7%8D%E5%B9%B3%E9%9D%A2/10788078
+* https://stackoverflow.com/questions/27331819/whats-the-difference-between-a-character-a-code-point-a-glyph-and-a-grapheme
+*/
+
+static std::string s_FontError;
+
+static std::vector<unsigned char> s_PixelBuffer;
+static const int s_SizePerPixel = sizeof(unsigned char);
+
+Font::Font(std::string path, TextGeneratingSettings settings)
+ : m_Characters()
+{
+ m_AtlasMargin = settings.margin;
+ m_GlyphPadding = settings.padding;
+ m_AtlasSize = settings.atlasSize;
+
+ if (FT_Init_FreeType(&m_FTLibrary))
+ {
+ s_FontError = "Init freetype error. Font path " + path;
+ throw FontException(s_FontError.c_str());
+ }
+
+ if (FT_New_Face(m_FTLibrary, path.c_str(), 0, &m_FTFace))
+ {
+ s_FontError = "Create freetype face error. Font path " + path;
+ throw FontException(s_FontError.c_str());
+ }
+}
+
+Font::Font(DataBuffer* db, TextGeneratingSettings settings)
+ : m_Characters()
+{
+ m_AtlasMargin = settings.margin;
+ m_GlyphPadding = settings.padding;
+ m_AtlasSize = settings.atlasSize;
+
+ if (FT_Init_FreeType(&m_FTLibrary))
+ {
+ s_FontError = "Init freetype error.";
+ throw FontException(s_FontError.c_str());
+ }
+
+ if (FT_New_Memory_Face(m_FTLibrary, db->udata, db->length, 0, &m_FTFace))
+ {
+ s_FontError = "Create freetype face error.";
+ throw FontException(s_FontError.c_str());
+ }
+}
+
+character::Hash Font::GetHash(Unicode codepoint, int pixelSize)
+{
+ character::Hash hash;
+ hash.codepoint = codepoint;
+ hash.size = pixelSize;
+ return hash;
+}
+
+const Character* Font::GetCharacter(character::Unicode codepoint, int pixelSize)
+{
+ character::Hash hash = GetHash(codepoint, pixelSize);
+ auto iter = m_Characters.find(hash.hashCode);
+ if (iter == m_Characters.end())
+ {
+ if (RenderCharacter(codepoint, pixelSize))
+ {
+ iter = m_Characters.find(hash.hashCode);
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+ Assert(iter != m_Characters.end());
+ return &iter->second;
+}
+
+void Font::RenderCharacters(character::Unicode* codepoint, int n, int pixelSize)
+{
+ for (int i = 0; i < n; ++i)
+ {
+ RenderCharacter(codepoint[i], pixelSize);
+ }
+}
+
+void Font::RenderCharacters(std::vector<character::Unicode>& codepoint, int pixelSize)
+{
+ int n = codepoint.size();
+ for (int i = 0; i < n; ++i)
+ {
+ RenderCharacter(codepoint[i], pixelSize);
+ }
+}
+
+bool Font::RenderCharacter(character::Unicode codepoint, int pixelSize)
+{
+ character::Hash hash = GetHash(codepoint, pixelSize);
+ if (m_Characters.size() > 0 && m_Characters.count(hash.hashCode) != 0)
+ return true;
+ FT_Set_Pixel_Sizes(m_FTFace, 0, pixelSize);
+
+ // bug: 发现有时候渲染的结果是FT_PIXEL_MODE_MONO(1-bits),试过不同的flags组合还是不对,最后手动转换为8-bits
+
+ FT_Int32 flags = FT_LOAD_RENDER;
+ if (FT_Load_Char(m_FTFace, codepoint, flags))
+ return false;
+
+ Character character;
+
+ int w = m_FTFace->glyph->bitmap.width;
+ int h = m_FTFace->glyph->bitmap.rows;
+
+ const unsigned char* pixels = m_FTFace->glyph->bitmap.buffer;
+ if (pixels != NULL) // 有些字体中space、tab没有字形的
+ {
+ s_PixelBuffer.resize(w * h);
+ if (m_FTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
+ {
+ // 1-bit monochrome
+ int pitch = m_FTFace->glyph->bitmap.pitch;
+ for (int y = 0; y < h; ++y)
+ {
+ for (int x = 0; x < w; ++x)
+ {
+ int index = x + y * w;
+ s_PixelBuffer[index] = ((pixels[pitch * y + x / 8]) & (1 << (7 - x % 8))) ? 255 : 0;
+ }
+ }
+ }
+ else if (m_FTFace->glyph->bitmap.pixel_mode)
+ {
+ // 8-bit grayscale
+ memcpy(&s_PixelBuffer[0], pixels, s_SizePerPixel * w * h);
+ }
+
+ //TextHelper::print_glyph(&s_PixelBuffer[0], w, h);
+
+ GlyphAtals* atlas = RequestAtlas(pixelSize, Vector2f(w, h));
+ Assert(atlas);
+
+ Rect rect = GetRenderChartAndMove(atlas, Vector2f(w, h));
+
+ try
+ {
+ atlas->altas->UpdateSubImage(rect, EPixelFormat::PixelFormat_R, EPixelElementType::PixelType_UNSIGNED_BYTE, &s_PixelBuffer[0]);
+ }
+ catch (TextureException& e)
+ {
+ s_PixelBuffer.clear();
+ std::string error = e.what();
+ error = "Render Character Error: " + error;
+ log_error(e.what());
+ return false;
+ }
+ s_PixelBuffer.clear();
+
+ character.atlas = atlas->index;
+ character.position = rect;
+ character.bearing = Vector2f(m_FTFace->glyph->bitmap_left, m_FTFace->glyph->bitmap_top);
+ }
+ else // space、tab
+ {
+ character.atlas = FONT_NOT_IN_ATLAS_PLACEHOLDER;
+ character.position = Rect(0,0,0,0);
+ character.bearing = Vector2f(0, 0);
+ }
+
+ character.advance = m_FTFace->glyph->advance.x * 1/64.f;
+
+ m_Characters.insert(std::pair<unsigned int, Character>(hash.hashCode, character));
+
+ return true;
+}
+
+const GlyphAtals* Font::GetGlyphAtlas(int index)
+{
+ if (index >= m_Atlases.size())
+ return NULL;
+ return &m_Atlases[index];
+}
+
+Rect Font::GetRenderChartAndMove(GlyphAtals* atlas, Vector2f preferSize)
+{
+ Rect rect;
+ Vector2f space;
+ space.x = atlas->width - atlas->cursor.x - m_AtlasMargin;
+ space.y = atlas->height - atlas->cursor.y - m_AtlasMargin;
+ if (space.x > preferSize.x && space.y > preferSize.y)
+ {
+ rect.x = atlas->cursor.x;
+ rect.y = atlas->cursor.y;
+ rect.width = preferSize.x;
+ rect.height = preferSize.y;
+ atlas->cursor.x += rect.width + m_GlyphPadding;
+ atlas->rowHeight = MathUtils::Max((float)atlas->rowHeight, preferSize.y);
+ }
+ else if (space.y - atlas->rowHeight - m_GlyphPadding - m_AtlasMargin > preferSize.y)
+ {
+ rect.x = m_AtlasMargin;
+ rect.y = atlas->cursor.y + m_GlyphPadding + atlas->rowHeight;
+ rect.width = preferSize.x;
+ rect.height = preferSize.y;
+ atlas->cursor.x = m_AtlasMargin;
+ atlas->cursor.y += atlas->rowHeight + m_GlyphPadding;
+ atlas->rowHeight = rect.height;
+ }
+ else
+ {
+ Assert(false);
+ }
+ return rect;
+}
+
+GlyphAtals* Font::RequestAtlas(int pixelSize, Vector2f preferSize)
+{
+ GlyphAtals* atlas = NULL;
+ auto iter = m_AtlasCache.find(pixelSize);
+ if (iter == m_AtlasCache.end() || !HasEnoughSpace(iter->second, preferSize))
+ {
+ Assert(m_Atlases.size() < FONT_NOT_IN_ATLAS_PLACEHOLDER);
+
+ Texture* tex = CreateAtlas();
+ GlyphAtals newAtlas = GlyphAtals();
+ newAtlas.altas = tex;
+ newAtlas.width = m_AtlasSize.x;
+ newAtlas.height = m_AtlasSize.y;
+ newAtlas.index = m_Atlases.size();
+ newAtlas.cursor = Vector2f(m_AtlasMargin, m_AtlasMargin);
+
+ m_Atlases.push_back(newAtlas);
+ atlas = &m_Atlases[m_Atlases.size() - 1];
+
+ if (iter != m_AtlasCache.end())
+ m_AtlasCache.erase(pixelSize);
+ m_AtlasCache.insert(std::pair<int, GlyphAtals*>(pixelSize, atlas));
+ }
+ else
+ {
+ atlas = iter->second;
+ }
+ Assert(atlas);
+ return atlas;
+}
+
+Texture* Font::CreateAtlas()
+{
+ TextureSetting setting;
+ setting.filterMode = ETextureFilterMode::Nearest;
+ setting.wrapMode = ETextureWrapMode::Clamp;
+ setting.format = ETextureFormat::R8;
+ setting.type = ETextureType::TEX_2D;
+ setting.keepImageData = false;
+ Texture* tex = new Texture(setting, m_AtlasSize.x, m_AtlasSize.y);
+ return tex;
+}
+
+bool Font::HasEnoughSpace(GlyphAtals* atlas, Vector2f preferSize)
+{
+ if (atlas == NULL)
+ return false;
+ Vector2f space;
+ space.x = atlas->width - atlas->cursor.x - m_AtlasMargin;
+ space.y = atlas->height - atlas->cursor.y - m_AtlasMargin;
+ if (space.x > preferSize.x && space.y > preferSize.y)
+ return true;
+ if (space.y - atlas->rowHeight - m_GlyphPadding - m_AtlasMargin > preferSize.y)
+ return true;
+ return false;
+}
+
+Character GetCharacter(character::Unicode Unicode, int pixelSize)
+{
+ return Character();
+}
+
+void TextHelper::print_glyph(unsigned char* glyph, int width, int height)
+{
+ for (int r = 0; r < height; ++r)
+ {
+ for (int c = 0; c < width; ++c)
+ {
+ unsigned char ch = glyph[c + r * width];
+ printf("%c", ch == 0 ? ' ' : '+');
+ }
+ printf("\n");
+ }
+}
diff --git a/Client/Source/GUI/Font.h b/Client/Source/GUI/Font.h
new file mode 100644
index 0000000..6fd3c19
--- /dev/null
+++ b/Client/Source/GUI/Font.h
@@ -0,0 +1,149 @@
+#pragma once
+
+#include "../Utilities/Singleton.h"
+#include "../Graphics/Texture.h"
+#include "../Common/DataBuffer.h"
+#include "../Math/Math.h"
+
+#include "freetype.h"
+
+#include <string>
+#include <unordered_map>
+#include <exception>
+#include <vector>
+
+//https://github.com/kaienfr/Font
+
+struct Character {
+ unsigned int atlas; // atlas索引
+ Rect position; // 在altas里的位置,以左上角为原点的,和GL的Y镜像
+ Vector2f bearing; // 左上角相对于原点的偏移
+ unsigned int advance; // 总宽,算上了间隔
+};
+
+namespace character
+{
+#if GAMELAB_WIN
+ typedef unsigned short Unicode; // unicode codepoint(BMP,U+0000至U+FFFF)
+#else
+ typedef unsigned int Unicode; // unicode codepoint(BMP,U+0000至U+FFFF)
+#endif
+
+#pragma pack(1)
+ union Hash {
+ unsigned int hashCode;
+ struct {
+ Unicode codepoint;
+ unsigned short size;//字体大小
+ };
+#pragma pack(pop)
+
+ bool operator==(const Hash &other) const
+ {
+ return codepoint == other.codepoint && size == other.size;
+ }
+ };
+}
+
+struct UnicodeString
+{
+ character::Unicode* str;
+ int length;
+};
+
+namespace std
+{
+ template <>
+ struct hash<character::Hash>
+ {
+ std::size_t operator()(const character::Hash& k) const
+ {
+ return k.hashCode;
+ }
+ };
+}
+
+struct GlyphAtals
+{
+ int index;
+ Texture* altas; // 贴图
+ int width, height; // 尺寸
+
+ Vector2f cursor; // 游标,从左上角(0,0)开始
+ int rowHeight; // 当前行的高度
+};
+
+struct TextGeneratingSettings
+{
+ Vector2f atlasSize; // atlas的尺寸
+ int margin; // atlas的边界
+ int padding; // glyph相互之间的间距,防止采样的时候越界
+};
+
+class FontException : public std::exception
+{
+public:
+ FontException(const char* what)
+ : std::exception(what)
+ {
+ }
+};
+
+enum EEncoding
+{
+ Encoding_ASCII,
+ Encoding_UTF8,
+ Encoding_UTF16,
+};
+
+// 没有字形,但是有advance的文字,比如space和tab
+#define FONT_NOT_IN_ATLAS_PLACEHOLDER (INT_MAX)
+
+// 单个字体
+class Font
+{
+public:
+ Font(std::string path, TextGeneratingSettings setting)/*throw FontException*/;
+ Font(DataBuffer* db, TextGeneratingSettings setting)/*throw FontException*/;
+
+ const Character* GetCharacter(character::Unicode codepoint, int pixelSize);
+ const GlyphAtals* GetGlyphAtlas(int index);
+
+ // pre-bake
+ bool RenderCharacter(character::Unicode codepoint, int pixelSize);
+ void RenderCharacters(character::Unicode* codepoint, int n, int pixelSize);
+ void RenderCharacters(std::vector<character::Unicode>& codepoint, int pixelSize);
+
+ GET(Vector2f, AtlasSize, m_AtlasSize);
+
+private:
+ Texture* CreateAtlas();
+ GlyphAtals* RequestAtlas(int pixelSize, Vector2f preferSize);
+ Rect GetRenderChartAndMove(GlyphAtals* atlas, Vector2f preferSize);
+ bool HasEnoughSpace(GlyphAtals* atlas, Vector2f preferSize);
+ character::Hash GetHash(character::Unicode Unicode, int pixelSize);
+
+ //-------------------------------------------------------------------------
+
+ std::unordered_map<unsigned int , Character> m_Characters; // 渲染完的文字
+
+ std::vector<GlyphAtals> m_Atlases; // 当前所有的atlas,由font完全拥有所有权,所以是lightuserdata
+ std::unordered_map<int/*pixelSize*/, GlyphAtals*> m_AtlasCache; // 快速找到可用的atlas
+
+ bool m_IsEnabled;
+
+ Vector2f m_AtlasSize;
+ int m_AtlasMargin;
+ int m_GlyphPadding;
+
+ FT_Library m_FTLibrary;
+ FT_Face m_FTFace;
+
+};
+
+namespace TextHelper
+{
+
+ void print_glyph(unsigned char* glyph, int width, int height);
+
+}
diff --git a/Client/Source/GUI/TextMeshGenerator.cpp b/Client/Source/GUI/TextMeshGenerator.cpp
new file mode 100644
index 0000000..766a9b0
--- /dev/null
+++ b/Client/Source/GUI/TextMeshGenerator.cpp
@@ -0,0 +1,89 @@
+#include "TextMeshGenerator.h"
+
+// 计算一个哈希值
+static uint32_t GetStrHash(const UnicodeString& str)
+{
+ if (str.length == 0)
+ return 0;
+
+ int len = str.length;
+ uint32_t hash = (uint32_t)len; // seed
+ size_t step = (len >> 5) + 1;
+ for (int l = len; l >= step; l -= step)
+ {
+ unsigned short unicode = str.str[l - 1];
+ unsigned char hicode = *((unsigned char*)&unicode);
+ unsigned char locode = *(((unsigned char*)&unicode) + 1);
+ hash = hash ^ ((hash << 5) + (hash >> 2) + hicode + locode);
+ }
+ return hash;
+}
+
+const UITextMesh* TextMeshGenerator::GetTextMesh(
+ const UnicodeString& str
+ , Font* font
+ , int pixelSize
+ , int lineHeight
+ , Color32 col32
+ , ETextAnchor anchor
+ , ETextAlignment alignment
+ , bool wordwrap
+ , float preferred
+){
+ uint32_t hash = GetStrHash(str);
+ UITextMeshList* tm = NULL;
+ bool hasHash = m_TextMeshes.count(hash);
+ if (hasHash)
+ {
+ tm = m_TextMeshes[hash];
+ while (tm)
+ {
+ UITextMesh* mesh = tm->mesh;
+ if (mesh->GetFont() != font
+ || mesh->GetPixelSize() != pixelSize
+ || mesh->GetLineHeight() != lineHeight
+ || mesh->GetAnchor() != anchor
+ || mesh->GetAlignment() != alignment
+ || mesh->GetWordwrap() != wordwrap
+ || mesh->GetPreferred() != preferred
+ || mesh->GetColor() != col32
+ ){
+ tm = tm->next;
+ continue;
+ }
+ const UnicodeString& content = mesh->GetContent();
+ if (content.length != str.length)
+ {
+ tm = tm->next;
+ continue;
+ }
+ if (memcmp(str.str, content.str, sizeof(character::Unicode) * str.length) == 0)
+ {
+ return tm->mesh;
+ }
+ tm = tm->next;
+ }
+ }
+ tm = new UITextMeshList();
+ try {
+ tm->mesh = new UITextMesh(str, font, pixelSize, lineHeight, col32, anchor, alignment, wordwrap, preferred);
+ log_info("Text", "New UITextMesh");
+ }
+ catch(TextMeshException& e)
+ {
+ delete tm;
+ throw;
+ }
+ if (hasHash)
+ {
+ UITextMeshList* list = m_TextMeshes[hash];
+ tm->next = list;
+ m_TextMeshes[hash] = tm;
+ }
+ else
+ {
+ tm->next = NULL;
+ m_TextMeshes.insert(std::pair<uint32_t, UITextMeshList*>(hash, tm));
+ }
+ return tm->mesh;
+}
diff --git a/Client/Source/GUI/TextMeshGenerator.h b/Client/Source/GUI/TextMeshGenerator.h
new file mode 100644
index 0000000..acc0a0a
--- /dev/null
+++ b/Client/Source/GUI/TextMeshGenerator.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <vector>
+#include <unordered_map>
+
+#include "../Debug/Log.h"
+#include "../Graphics/Color.h"
+
+#include "UITextMesh.h"
+#include "Font.h"
+
+// 逐步回收长期没用到的textmesh
+class GraduallyReleaseTextMesh
+{
+};
+
+struct UITextMeshList {
+ UITextMesh* mesh;
+ UITextMeshList* next;
+};
+
+class TextMeshGenerator : public Singleton<TextMeshGenerator>
+{
+public:
+ const UITextMesh* GetTextMesh(const UnicodeString& str, Font* font, int pixelSize, int lineHeight, Color32 col32, ETextAnchor anchor, ETextAlignment alignment, bool wordwrap, float preferred);
+
+private:
+ std::unordered_map<uint32_t, UITextMeshList*> m_TextMeshes;
+};
+
+#define g_TextMeshGenerator (*TextMeshGenerator::Instance()) \ No newline at end of file
diff --git a/Client/Source/GUI/UI9Slicing.cpp b/Client/Source/GUI/UI9Slicing.cpp
new file mode 100644
index 0000000..c0d8f60
--- /dev/null
+++ b/Client/Source/GUI/UI9Slicing.cpp
@@ -0,0 +1,112 @@
+#include "../Math/Math.h"
+
+#include "UI9Slicing.h"
+
+#include <cstring>
+#include <vector>
+
+using namespace std;
+
+static vector<UIVertexLayout> s_Vertices;
+static vector<UIIndex> s_Indices;
+
+UI9Slicing::UI9Slicing(int mode, Vector2f horizontal, Vector2f vertical, Vector2f texPixelSize, Vector2f size)
+{
+ m_Slicing = mode;
+ m_Horizontal = horizontal.Clamp(0, texPixelSize.x, 0, texPixelSize.x);
+ m_Vertical = vertical.Clamp(0, texPixelSize.y, 0, texPixelSize.y);
+
+ if (m_Horizontal[0] + m_Horizontal[1] > texPixelSize.x || m_Vertical[0] + m_Vertical[1] > texPixelSize.y)
+ {
+ throw UIMeshException("UI9Slicing wrong parameter.");
+ }
+ m_TexSize = texPixelSize;
+ m_Size = size;
+}
+
+void UI9Slicing::Draw()
+{
+ s_Indices.clear();
+ s_Vertices.clear();
+
+ Vector2f tileSize = Vector2f(m_TexSize.x - m_Horizontal[0] - m_Horizontal[1], m_TexSize.y - m_Vertical[0] - m_Vertical[1]);
+ Vector2f fillSize = Vector2f(m_Size.x - m_Horizontal[0] - m_Horizontal[1], m_Size.y - m_Vertical[0] - m_Vertical[1]);
+ // horizonal和vertical对应的uv,注意uv在左下角,mesh在左上角
+ Vector2f tileUVx = Vector2f(m_Horizontal[0] / m_TexSize.x, (m_TexSize.x - m_Horizontal[1]) / m_TexSize.x);
+ Vector2f tileUVy = Vector2f((m_TexSize.y - m_Vertical[0]) / m_TexSize.y, m_Vertical[1] / m_TexSize.y);
+
+ // fill vertices
+ int row = 2 + ((fillSize.y <= 0) ? 0 : (m_Slicing == Slicing_Simple ? 2 : ceilf((float)fillSize.y / tileSize.y) + 1));
+ int colum = 2 + ((fillSize.x <= 0) ? 0 : (m_Slicing == Slicing_Simple ? 2 : ceilf((float)fillSize.x / tileSize.x) + 1));
+
+ for (int r = 0; r < row; ++r)
+ {
+ UIVertexLayout vert;
+ vert.color = Color32::white;
+ if (r == 0)
+ {
+ vert.position.y = 0;
+ vert.uv.y = 1;
+ }
+ else if (r == row - 1)
+ {
+ vert.position.y = m_Size.y;
+ vert.uv.y = 0;
+ }
+ else {
+ if (m_Slicing == Slicing_Tiled) {
+ vert.position.y = clamp(m_Vertical[0] + (r - 1) * tileSize.y, m_Vertical[0], m_Size.y - m_Vertical[1]);
+ vert.uv.y = odd(r - 1) ? tileUVy[1] : tileUVy[0];
+ } else {
+ vert.position.y = odd(r - 1) ? m_Size.y - m_Vertical[1] : m_Vertical[0];
+ vert.uv.y = odd(r - 1) ? tileUVy[1] : tileUVy[0];
+ }
+ }
+ for (int c = 0; c < colum; ++c)
+ {
+ if (c == 0)
+ {
+ vert.position.x = 0;
+ vert.uv.x = 0;
+ }
+ else if (c == colum - 1)
+ {
+ vert.position.x = m_Size.x;
+ vert.uv.x = 1;
+ }
+ else {
+ if (m_Slicing == Slicing_Tiled) {
+ vert.position.x = clamp(m_Horizontal[0] + (c - 1) * tileSize.x, m_Horizontal[0], m_Size.x - m_Horizontal[0]);
+ vert.uv.x = odd(c - 1) ? tileUVx[1] : tileUVx[0];
+ } else {
+ vert.position.x = odd(c - 1) ? (m_Size.x - m_Horizontal[1]) : m_Horizontal[0];
+ vert.uv.x = odd(c - 1) ? tileUVx[1] : tileUVx[0];
+ }
+ }
+ s_Vertices.push_back(vert);
+
+ if (c < colum - 1 && r < row - 1) {
+ int index = c + r * colum;
+ s_Indices.push_back(index); s_Indices.push_back(index + colum); s_Indices.push_back(index + colum + 1);
+ s_Indices.push_back(index + colum + 1); s_Indices.push_back(index + 1); s_Indices.push_back(index);
+ }
+ }
+ }
+
+ void* vb;
+ void* ib;
+
+ int vertCount = s_Vertices.size();
+ int indicesCount = s_Indices.size();
+
+ g_SharedVBO.GetChunk(sizeof(UIVertexLayout), sizeof(UIIndex), vertCount, indicesCount, Primitive_Triangle, &vb, &ib);
+
+ memcpy(vb, &s_Vertices[0], vertCount * sizeof(UIVertexLayout));
+ memcpy(ib, &s_Indices[0], indicesCount* sizeof(UIIndex));
+
+ s_Indices.resize(0);
+ s_Vertices.resize(0);
+
+ g_SharedVBO.ReleaseChunk(vertCount, indicesCount);
+ g_SharedVBO.DrawChunk(s_UIVertexLayout);
+} \ No newline at end of file
diff --git a/Client/Source/GUI/UI9Slicing.h b/Client/Source/GUI/UI9Slicing.h
new file mode 100644
index 0000000..6a37447
--- /dev/null
+++ b/Client/Source/GUI/UI9Slicing.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "UIMesh.h"
+
+enum ESlicing
+{
+ Slicing_Simple,
+ Slicing_Tiled,
+};
+
+// 九宫格
+class UI9Slicing : public UIMesh
+{
+public:
+ UI9Slicing(int mode, Vector2f horizontal, Vector2f vertical, Vector2f texPixelSize, Vector2f size)/*throw UIMeshException*/;
+
+ void Draw() override;
+
+private:
+ int m_Slicing;
+ Vector2f m_Horizontal; // 左右两条切割线到左边和右边的距离
+ Vector2f m_Vertical; // 上下两条切割线到上下的距离
+ Vector2f m_TexSize;
+ Vector2f m_Size;
+};
diff --git a/Client/Source/GUI/UILine.cpp b/Client/Source/GUI/UILine.cpp
new file mode 100644
index 0000000..8aa8c1d
--- /dev/null
+++ b/Client/Source/GUI/UILine.cpp
@@ -0,0 +1,48 @@
+#include "UILine.h"
+
+struct UILineLayout
+{
+ Vector2f position;
+};
+
+static CustomVertexLayout layout;
+
+InitializeStaticVariables([]() {
+ VertexAttributeDescriptor POSITION = VertexAttributeDescriptor(0, 2, VertexAttrFormat_Float, sizeof(UILineLayout));
+
+ layout.attributes.push_back(POSITION);
+});
+
+void UILine::Draw()
+{
+ const int nVerts = 2;
+ const int nIndices = 2;
+
+ float pos[] = {
+ m_From.x, m_From.y,
+ m_To.x, m_To.y,
+ };
+
+ uint16 indices[] = {
+ 0, 1
+ };
+
+
+ uint8* vb;
+ uint16* ib;
+
+ g_SharedVBO.GetChunk(sizeof(UILineLayout), sizeof(uint16), nVerts, nIndices, Primitive_Line, (void**)&vb, (void**)&ib);
+
+ UILineLayout* dst = (UILineLayout*)vb;
+
+ for (int i = 0; i < nVerts; ++i)
+ {
+ dst[i].position.Set(pos[2 * i], pos[2 * i + 1]);
+ }
+
+ for (int i = 0; i < nIndices; ++i)
+ ib[i] = indices[i];
+
+ g_SharedVBO.ReleaseChunk(nVerts, nIndices);
+ g_SharedVBO.DrawChunk(layout);
+} \ No newline at end of file
diff --git a/Client/Source/GUI/UILine.h b/Client/Source/GUI/UILine.h
new file mode 100644
index 0000000..a47b1f1
--- /dev/null
+++ b/Client/Source/GUI/UILine.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../Utilities/StaticInitiator.h"
+#include "UIMesh.h"
+
+class UILine : public UIMesh
+{
+public:
+ UILine(Vector2f from, Vector2f to)
+ {
+ m_From = from;
+ m_To = to;
+ }
+
+ void Draw() override;
+
+private:
+ Vector2f m_From;
+ Vector2f m_To;
+
+};
diff --git a/Client/Source/GUI/UIMesh.cpp b/Client/Source/GUI/UIMesh.cpp
new file mode 100644
index 0000000..9616c46
--- /dev/null
+++ b/Client/Source/GUI/UIMesh.cpp
@@ -0,0 +1,15 @@
+#include "UIMesh.h"
+
+CustomVertexLayout UIMesh::s_UIVertexLayout;
+unsigned int UIMesh::s_SizePerVertex;
+
+InitializeStaticVariables([]() {
+ VertexAttributeDescriptor POSITION = VertexAttributeDescriptor(0, 2, VertexAttrFormat_Float, sizeof(UIVertexLayout));
+ VertexAttributeDescriptor UV = VertexAttributeDescriptor(sizeof(Vector2f), 2, VertexAttrFormat_Float, sizeof(UIVertexLayout));
+ VertexAttributeDescriptor COLOR = VertexAttributeDescriptor(sizeof(Vector2f) * 2, 4, VertexAttrFormat_Unsigned_Byte, sizeof(UIVertexLayout), true);
+ UIMesh::s_UIVertexLayout.attributes.push_back(POSITION);
+ UIMesh::s_UIVertexLayout.attributes.push_back(UV);
+ UIMesh::s_UIVertexLayout.attributes.push_back(COLOR);
+
+ UIMesh::s_SizePerVertex = sizeof(UIVertexLayout);
+});
diff --git a/Client/Source/GUI/UIMesh.h b/Client/Source/GUI/UIMesh.h
new file mode 100644
index 0000000..11f157a
--- /dev/null
+++ b/Client/Source/GUI/UIMesh.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "../Utilities/Exception.h"
+#include "../Graphics/DynamicMesh.h"
+#include "../Math/Math.h"
+#include "../Utilities/StaticInitiator.h"
+#include "../Graphics/CustomVertexLayout.h"
+#include "../Graphics/DefaultVertexLayout.h"
+#include "../Graphics/Color.h"
+#include "../Graphics/GfxDevice.h"
+
+// UI默认的顶点布局
+struct UIVertexLayout
+{
+ UIVertexLayout(Vector2f pos = Vector2f::zero, Vector2f texCoord = Vector2f::zero, Color32 col = Color32())
+ {
+ position = pos;
+ uv = texCoord;
+ color = col;
+ }
+
+ Vector2f position;
+ Vector2f uv;
+ Color32 color;
+};
+
+typedef uint16 UIIndex;
+
+CustomException(UIMeshException);
+
+// 所有的UIMesh都是左上角为原点
+class UIMesh : public DynamicMesh
+{
+public:
+ UIMesh() : DynamicMesh() {}
+ virtual ~UIMesh() {}
+
+ virtual void Draw() = 0;
+
+ static CustomVertexLayout s_UIVertexLayout;
+ static unsigned int s_SizePerVertex;
+};
diff --git a/Client/Source/GUI/UIQuad.cpp b/Client/Source/GUI/UIQuad.cpp
new file mode 100644
index 0000000..d3c7825
--- /dev/null
+++ b/Client/Source/GUI/UIQuad.cpp
@@ -0,0 +1,64 @@
+#include "../Math/Math.h"
+#include "../Graphics/GfxDevice.h"
+
+#include "UIQuad.h"
+
+struct UIQuadLayout
+{
+ Vector2f position;
+ Vector2f uv;
+};
+
+static CustomVertexLayout layout;
+
+InitializeStaticVariables([]() {
+ VertexAttributeDescriptor POSITION = VertexAttributeDescriptor(0, 2, VertexAttrFormat_Float, sizeof(UIQuadLayout));
+ VertexAttributeDescriptor UV = VertexAttributeDescriptor(sizeof(Vector2f), 2, VertexAttrFormat_Float, sizeof(UIQuadLayout));
+
+ layout.attributes.push_back(POSITION);
+ layout.attributes.push_back(UV);
+});
+
+void UIQuad::Draw()
+{
+ const int nVerts = 4;
+ const int nIndices = 6;
+
+ float pos[] = {
+ m_Left, m_Bottom, // left-bottom
+ m_Right, m_Bottom, // right-bottom
+ m_Right, m_Top, // right-top
+ m_Left, m_Top, // top-left
+ };
+
+ float uv[] = {
+ 0, 0,
+ 1, 0,
+ 1, 1,
+ 0, 1,
+ };
+
+ uint16 indices[] = {
+ 0, 1, 3, // right-top
+ 1, 2, 3, // left-bottom
+ };
+
+ uint8* vb;
+ uint16* ib;
+
+ g_SharedVBO.GetChunk(sizeof(UIQuadLayout), sizeof(uint16), nVerts, nIndices, Primitive_Triangle, (void**)&vb, (void**)&ib);
+
+ UIQuadLayout* dst = (UIQuadLayout*)vb;
+
+ for (int i = 0; i < nVerts; ++i)
+ {
+ dst[i].position.Set(pos[2 * i], pos[2 * i + 1]);
+ dst[i].uv.Set(uv[2 * i], uv[2 * i + 1]);
+ }
+
+ for (int i = 0; i < nIndices; ++i)
+ ib[i] = indices[i];
+
+ g_SharedVBO.ReleaseChunk(nVerts, nIndices);
+ g_SharedVBO.DrawChunk(layout);
+} \ No newline at end of file
diff --git a/Client/Source/GUI/UIQuad.h b/Client/Source/GUI/UIQuad.h
new file mode 100644
index 0000000..278848a
--- /dev/null
+++ b/Client/Source/GUI/UIQuad.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "../Utilities/StaticInitiator.h"
+#include "UIMesh.h"
+
+class UIQuad : public UIMesh
+{
+public :
+ UIQuad(float l, float r, float t, float b)
+ {
+ m_Left = l;
+ m_Right = r;
+ m_Top = t;
+ m_Bottom = b;
+ }
+
+ void Draw() override;
+
+private:
+ float m_Left, m_Right, m_Top, m_Bottom;
+
+};
diff --git a/Client/Source/GUI/UISquare.cpp b/Client/Source/GUI/UISquare.cpp
new file mode 100644
index 0000000..f5c3702
--- /dev/null
+++ b/Client/Source/GUI/UISquare.cpp
@@ -0,0 +1,53 @@
+#include "../Math/Math.h"
+#include "../Graphics/GfxDevice.h"
+
+#include "UISquare.h"
+
+struct UISquareLayout
+{
+ Vector2f position;
+};
+
+static CustomVertexLayout layout;
+
+InitializeStaticVariables([]() {
+ VertexAttributeDescriptor POSITION = VertexAttributeDescriptor(0, 2, VertexAttrFormat_Float, sizeof(UISquareLayout));
+
+ layout.attributes.push_back(POSITION);
+});
+
+void UISquare::Draw()
+{
+ const int nVerts = 4;
+ const int nIndices = 6;
+
+ float pos[] = {
+ m_Left, m_Bottom, // left-bottom
+ m_Right, m_Bottom, // right-bottom
+ m_Right, m_Top, // right-top
+ m_Left, m_Top, // top-left
+ };
+
+ int indices[] = {
+ 0, 1, 3, // right-top
+ 1, 2, 3, // left-bottom
+ };
+
+ uint8* vb;
+ uint16* ib;
+
+ g_SharedVBO.GetChunk(sizeof(UISquareLayout), sizeof(uint16), 4, 6, Primitive_Triangle, (void**)&vb, (void**)&ib);
+
+ UISquareLayout* dst = (UISquareLayout*)vb;
+
+ for (int i = 0; i < nVerts; ++i)
+ {
+ dst[i].position.Set(pos[2 * i], pos[2 * i + 1]);
+ }
+
+ for (int i = 0; i < nIndices; ++i)
+ ib[i] = indices[i];
+
+ g_SharedVBO.ReleaseChunk(4, 6);
+ g_SharedVBO.DrawChunk(layout);
+}
diff --git a/Client/Source/GUI/UISquare.h b/Client/Source/GUI/UISquare.h
new file mode 100644
index 0000000..8de07f0
--- /dev/null
+++ b/Client/Source/GUI/UISquare.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "../Utilities/StaticInitiator.h"
+#include "UIMesh.h"
+
+// 纯色方形
+class UISquare : public UIMesh
+{
+public:
+ UISquare(float l, float r, float t, float b)
+ {
+ m_Left = l;
+ m_Right = r;
+ m_Top = t;
+ m_Bottom = b;
+ }
+
+ void Draw() override;
+
+private:
+ float m_Left, m_Right, m_Top, m_Bottom;
+
+};
diff --git a/Client/Source/GUI/UITextMesh.cpp b/Client/Source/GUI/UITextMesh.cpp
new file mode 100644
index 0000000..13bd8dc
--- /dev/null
+++ b/Client/Source/GUI/UITextMesh.cpp
@@ -0,0 +1,278 @@
+#include "../Graphics/CustomVertexLayout.h"
+#include "../Utilities/StaticInitiator.h"
+#include "../Math/Math.h"
+#include "../Graphics/Color.h"
+#include "../Graphics/GfxDevice.h"
+#include "../Utilities/AutoInvoke.h"
+#include "../Graphics/DefaultVertexLayout.h"
+#include "../Debug/Log.h"
+
+#include "UITextMesh.h"
+
+#include <vector>
+#include <unordered_map>
+
+using namespace std;
+
+struct TextMeshVBOLayout
+{
+ Vector2f position;
+ Vector2f uv;
+ Color32 color;
+};
+
+static CustomVertexLayout s_TextMeshVBOLayout;
+
+static unsigned int s_VertexPerText;
+static unsigned int s_SizePerVertex;
+static unsigned int s_SizePerText;
+static unsigned int s_SizePerIndex;
+static unsigned int s_IndicesPerText;
+
+struct TextInfo {
+ const Character* ch;
+ float offset;
+ int line; // 从0开始
+};
+
+InitializeStaticVariables([]() {
+ VertexAttributeDescriptor POSITION = VertexAttributeDescriptor(0, 2, VertexAttrFormat_Float, sizeof(TextMeshVBOLayout));
+ VertexAttributeDescriptor UV = VertexAttributeDescriptor(sizeof(Vector2f), 2, VertexAttrFormat_Float, sizeof(TextMeshVBOLayout));
+ VertexAttributeDescriptor COLOR = VertexAttributeDescriptor(sizeof(Vector2f)*2, 4, VertexAttrFormat_Unsigned_Byte, sizeof(TextMeshVBOLayout), true);
+ s_TextMeshVBOLayout.attributes.push_back(POSITION);
+ s_TextMeshVBOLayout.attributes.push_back(UV);
+ s_TextMeshVBOLayout.attributes.push_back(COLOR);
+
+ s_VertexPerText = 4;
+ s_SizePerVertex = sizeof(TextMeshVBOLayout);
+
+ s_IndicesPerText = 6;
+ s_SizePerIndex = VertexLayout::GetDefaultIndexSize();
+
+ s_SizePerText = sizeof(TextMeshVBOLayout) * 4;
+});
+
+UITextMesh::UITextMesh(
+ const UnicodeString& str // 文本
+ , Font* font // 字体
+ , int pixelSize // 大小
+ , int lineHeight // 行高
+ , Color32 color32 // 颜色
+ , ETextAnchor anchor // 锚点
+ , ETextAlignment alignment // 对齐方式
+ , bool wordwrap // 自动换行
+ , float preferred // 自动换行区域大小
+){
+ m_Font = font;
+ m_PixelSize = pixelSize;
+ m_LineHeight = lineHeight;
+ m_Color = color32;
+ m_Alignment = alignment;
+ m_Anchor = anchor;
+ m_Wordwrap = wordwrap;
+ m_Preferred = preferred;
+ m_Content.length = str.length;
+ m_Content.str = (character::Unicode*)malloc(str.length * sizeof(character::Unicode));
+ memcpy(m_Content.str, str.str, str.length * sizeof(character::Unicode));
+
+ // 记录文本按atlas分类
+ static unordered_map<unsigned int, vector<TextInfo>> s_TextInfos;
+ s_TextInfos.clear();
+
+ // 记录文本每行的长度
+ static unordered_map<int, int> s_LineWidths;
+ s_LineWidths.clear();
+
+ // 记录每行的偏移长度
+ static unordered_map<int, int> s_LineOffsets;
+ s_LineOffsets.clear();
+
+ InvokeWhenLeave([]() {
+ s_LineWidths.clear();
+ s_LineOffsets.clear();
+ s_TextInfos.clear();
+ });
+
+ const Vector2f atlasSize = font->GetAtlasSize();
+
+ //-----------------------------------------------------------------
+ // 处理了换行和自动换行之后的文本区域大小
+ Vector2f textRegion;
+ // 按照不同的atlas分类到s_TextInfos
+ float offset = 0;
+ int line = 0;
+ for (int i = 0; i < str.length; ++i)
+ {
+ character::Unicode c = str.str[i];
+ const Character* ch = font->GetCharacter(c, pixelSize);
+ if (ch == NULL)
+ continue;
+
+ if (wordwrap) // 自动换行
+ {
+ if (offset + ch->bearing.x + ch->position.width > preferred)
+ {
+
+ ++line;
+ offset = 0;
+ }
+ }
+
+ unsigned int atlasIndex = ch->atlas;
+ if (atlasIndex != FONT_NOT_IN_ATLAS_PLACEHOLDER) //非空格
+ {
+// 换行符Unix'\n', Windows'\r\n', MacOS '\r'
+#define CHECK_BREAKS() \
+ if (c == '\n' || c == '\r') \
+ { \
+ ++line; \
+ offset = 0; \
+ if (c == '\r' && ((i + 1) < str.length && str.str[i + 1] == '\n')) /*skip\n*/ \
+ ++i; \
+ continue; \
+ }
+
+ CHECK_BREAKS();
+
+ TextInfo info;
+ info.ch = ch;
+ info.offset = offset;
+ info.line = line;
+
+ auto list = s_TextInfos.find(atlasIndex);
+ if (list == s_TextInfos.end())
+ s_TextInfos.insert(std::pair<unsigned int, vector<TextInfo>>(atlasIndex, vector<TextInfo>()));
+
+ vector<TextInfo>& v = s_TextInfos[atlasIndex];
+ v.push_back(info);
+ }
+ else
+ {
+ // 有些字体换行没有字形,所以也需要在这里处理
+ CHECK_BREAKS();
+ }
+
+ offset += ch->advance;
+
+ textRegion.x = max(offset, textRegion.x);
+
+ if (s_LineWidths.count(line) == 0)
+ s_LineWidths.insert(std::pair<int, int>(line, 0));
+ s_LineWidths[line] = max(offset, s_LineWidths[line]);
+ }
+
+ textRegion.y = (line + 1) * lineHeight;
+
+ if (s_TextInfos.size() == 0)
+ {
+ return;
+ }
+
+ Vector2i textOffset;
+ Vector2f halfRegion = Vector2f(textRegion.x/ 2.f, textRegion.y / 2.f);
+ switch (anchor)
+ {
+ case TextAnchor_UpperLeft: textOffset.Set(0, 0); break;
+ case TextAnchor_UpperCenter: textOffset.Set(-halfRegion.x, 0); break;
+ case TextAnchor_UpperRight: textOffset.Set(-textRegion.x, 0); break;
+ case TextAnchor_MiddleLeft: textOffset.Set(0, -halfRegion.y); break;
+ case TextAnchor_MiddleCenter: textOffset.Set(-halfRegion.x, -halfRegion.y); break;
+ case TextAnchor_MiddleRight: textOffset.Set(-textRegion.x, -halfRegion.y); break;
+ case TextAnchor_LowerLeft: textOffset.Set(0, -textRegion.y); break;
+ case TextAnchor_LowerCenter: textOffset.Set(-halfRegion.x, -textRegion.y); break;
+ case TextAnchor_LowerRight: textOffset.Set(-textRegion.x, -textRegion.y); break;
+ }
+
+ for (int i = 0; i < line; ++i)
+ {
+ int lineLen = s_LineWidths.count(i) != 0 ? s_LineWidths[i] : 0;
+ int lineOffset = 0;
+ switch (alignment)
+ {
+ case TextAlignment_Left: lineOffset = 0; break;
+ case TextAlignment_Center: lineOffset = (textRegion.x - lineLen)/2.f; break;
+ case TextAlignment_Right: lineOffset = textRegion.x - lineLen; break;
+ }
+ s_LineOffsets.insert(std::pair<int, int>(i, lineOffset));
+ }
+
+ //-----------------------------------------------------------------
+
+ // 填充VBO和IBO
+ for (auto iter : s_TextInfos) {
+ unsigned int atlasIndex = iter.first; // atlas atlasIndex
+ vector<TextInfo>& texts = iter.second;
+ int textCount = texts.size();
+
+ VertexBuffer* vb = new VertexBuffer(textCount * s_SizePerText, textCount * s_IndicesPerText * s_SizePerIndex, VertexBuffer::VertexBufferType_Static);
+ void* pVB;
+ uint16* pIB;
+
+ vb->GetChunk(s_SizePerVertex, s_SizePerIndex, s_VertexPerText * textCount, s_IndicesPerText * textCount, EPrimitive::Primitive_Triangle, &pVB,(void**) &pIB);
+
+ TextMeshVBOLayout* dst = (TextMeshVBOLayout*)pVB;
+ for (int i = 0; i < textCount; ++i)
+ {
+ TextInfo& text = texts[i];
+
+ int vOff = i * s_VertexPerText;
+ int lineXOff = s_LineOffsets.count(text.line) ? s_LineOffsets[text.line] : 0;
+ int lineYOff = text.line * lineHeight;
+ // 左上角是原点
+ float pos[] = {
+ textOffset.x + lineXOff + text.offset + text.ch->bearing.x, textOffset.y + lineYOff + pixelSize - text.ch->bearing.y + text.ch->position.height, // bottom-left
+ textOffset.x + lineXOff + text.offset + text.ch->bearing.x + text.ch->position.width, textOffset.y + lineYOff + pixelSize - text.ch->bearing.y + text.ch->position.height, // bottom-right
+ textOffset.x + lineXOff + text.offset + text.ch->bearing.x + text.ch->position.width, textOffset.y + lineYOff + pixelSize - text.ch->bearing.y, // top-right
+ textOffset.x + lineXOff + text.offset + text.ch->bearing.x, textOffset.y + lineYOff + pixelSize - text.ch->bearing.y, // top-left
+ };
+ Vector4f uvQuad = Vector4f(text.ch->position.x / atlasSize.x, text.ch->position.y / atlasSize.y, text.ch->position.width / atlasSize.x, text.ch->position.height / atlasSize.y);
+ float uv[] = {
+ uvQuad.x, uvQuad.y + uvQuad.w,
+ uvQuad.x + uvQuad.z, uvQuad.y + uvQuad.w,
+ uvQuad.x + uvQuad.z, uvQuad.y,
+ uvQuad.x, uvQuad.y,
+ };
+ for (int j = 0; j < s_VertexPerText; ++j)
+ {
+ dst[vOff + j].position.Set(pos[2 * j], pos[2 * j + 1]);
+ dst[vOff + j].uv.Set(uv[2 * j], uv[2 * j + 1]);
+ dst[vOff + j].color = color32;
+ }
+
+ int iOff = i * s_IndicesPerText;
+ int indices[] = {
+ 0, 1, 3, // right-top
+ 1, 2, 3, // left-bottom
+ };
+ for (int j = 0; j < s_IndicesPerText; ++j)
+ pIB[iOff + j] = vOff + indices[j];
+ }
+
+ vb->FlushChunk(s_VertexPerText * textCount, s_IndicesPerText * textCount);
+
+ m_VBOs.insert(std::pair<int, VertexBuffer*>(atlasIndex, vb));
+ }
+
+ WipeGLError();
+}
+
+void UITextMesh::Draw() const
+{
+ for (auto subText : m_VBOs)
+ {
+ int atlasIndex = subText.first; // atlasIndex of atlas
+ VertexBuffer* vbo = subText.second;
+
+ const GlyphAtals* atlas = m_Font->GetGlyphAtlas(atlasIndex);
+ if (atlas == NULL)
+ {
+ log_error("Render text failed, no glyph atlas.");
+ continue;
+ }
+
+ g_GfxDevice.SetUniformTexture("gamelab_main_tex", atlas->altas);
+
+ WipeGLError();
+ vbo->Draw(s_TextMeshVBOLayout);
+ }
+}
diff --git a/Client/Source/GUI/UITextMesh.h b/Client/Source/GUI/UITextMesh.h
new file mode 100644
index 0000000..869db80
--- /dev/null
+++ b/Client/Source/GUI/UITextMesh.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "../Graphics/VertexBuffer.h"
+#include "../Utilities/Exception.h"
+#include "../Graphics/Color.h"
+
+#include "Font.h"
+
+#include <unordered_map>
+
+CustomException(TextMeshException);
+
+enum ETextAnchor
+{
+ TextAnchor_UpperLeft,
+ TextAnchor_UpperCenter,
+ TextAnchor_UpperRight,
+ TextAnchor_MiddleLeft,
+ TextAnchor_MiddleCenter,
+ TextAnchor_MiddleRight,
+ TextAnchor_LowerLeft,
+ TextAnchor_LowerCenter,
+ TextAnchor_LowerRight,
+};
+
+enum ETextAlignment {
+ TextAlignment_Left,
+ TextAlignment_Center,
+ TextAlignment_Right,
+};
+
+namespace TextHelper
+{
+}
+
+class UITextMesh
+{
+public:
+ void Draw() const;
+
+private:
+ friend class TextMeshGenerator;
+
+ UITextMesh(const UnicodeString& str, Font* font, int pixelSize, int lineHeight, Color32 color32 = Color32::white, ETextAnchor anchor = TextAnchor_UpperLeft, ETextAlignment alignment = TextAlignment_Left, bool wordwrap = false, float preferred = 0)/*throw TextMeshException*/;
+ ~UITextMesh();
+
+ GET(const Font*, Font, m_Font);
+ GET(int, PixelSize, m_PixelSize);
+ GET(int, LineHeight, m_LineHeight);
+ GET(Color32, Color, m_Color);
+ GET(ETextAlignment, Alignment, m_Alignment);
+ GET(ETextAnchor, Anchor, m_Anchor);
+ GET(bool, Wordwrap, m_Wordwrap);
+ GET(float, Preferred, m_Preferred);
+ GET(const UnicodeString&, Content, m_Content);
+
+ std::unordered_map<int, VertexBuffer*> m_VBOs;
+
+ Font* m_Font;
+ UnicodeString m_Content;
+ int m_PixelSize;
+ int m_LineHeight;
+ Color32 m_Color;
+ ETextAlignment m_Alignment;
+ ETextAnchor m_Anchor;
+ bool m_Wordwrap;
+ float m_Preferred;
+
+}; \ No newline at end of file
diff --git a/Client/Source/GUI/freetype.h b/Client/Source/GUI/freetype.h
new file mode 100644
index 0000000..4b54e20
--- /dev/null
+++ b/Client/Source/GUI/freetype.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
diff --git a/Client/Source/GUI/utf8.cpp b/Client/Source/GUI/utf8.cpp
new file mode 100644
index 0000000..8a3a086
--- /dev/null
+++ b/Client/Source/GUI/utf8.cpp
@@ -0,0 +1,415 @@
+/*
+ * Description: UTF-8 瀛楃涓茬殑瑙g爜鍜岀紪鐮佸嚱鏁
+ * unicode 瀛楃澶勭悊鍑芥暟
+ * History: yang@haipo.me, 2013/05/29, create
+ *
+ * This code is in the public domain.
+ * You may use this code any way you wish, private, educational,
+ * or commercial. It's free.
+ */
+
+# include <stdint.h>
+# include <stddef.h>
+
+# include "utf8.h"
+
+namespace utf8
+{
+
+ ucs4_t getu8c(char **src, int *illegal)
+ {
+ static char umap[256] = { 0 };
+ static int umap_init_flag = 0;
+
+ if (umap_init_flag == 0)
+ {
+ int i;
+
+ for (i = 0; i < 0x100; ++i)
+ {
+ if (i < 0x80)
+ {
+ umap[i] = 1;
+ }
+ else if (i >= 0xc0 && i < 0xe0)
+ {
+ umap[i] = 2;
+ }
+ else if (i >= 0xe0 && i < 0xf0)
+ {
+ umap[i] = 3;
+ }
+ else if (i >= 0xf0 && i < 0xf8)
+ {
+ umap[i] = 4;
+ }
+ else if (i >= 0xf8 && i < 0xfc)
+ {
+ umap[i] = 5;
+ }
+ else if (i >= 0xfc && i < 0xfe)
+ {
+ umap[i] = 6;
+ }
+ else
+ {
+ umap[i] = 0;
+ }
+ }
+
+ umap_init_flag = 1;
+ }
+
+ uint8_t *s = (uint8_t *)(*src);
+ int r_illegal = 0;
+
+ while (umap[*s] == 0)
+ {
+ ++s;
+ ++r_illegal;
+ }
+
+ uint8_t *t;
+ int byte_num;
+ uint32_t uc;
+ int i;
+
+ repeat_label:
+ t = s;
+ byte_num = umap[*s];
+ uc = *s++ & (0xff >> byte_num);
+
+ for (i = 1; i < byte_num; ++i)
+ {
+ if (umap[*s])
+ {
+ r_illegal += s - t;
+ goto repeat_label;
+ }
+ else
+ {
+ uc = (uc << 6) + (*s & 0x3f);
+ s += 1;
+ }
+ }
+
+ *src = (char *)s;
+ if (illegal)
+ {
+ *illegal = r_illegal;
+ }
+
+ return uc;
+ }
+
+ size_t u8decode(char const *str, ucs4_t *des, size_t n, int *illegal)
+ {
+ if (n == 0)
+ return 0;
+
+ char *s = (char *)str;
+ size_t i = 0;
+ ucs4_t uc = 0;
+ int r_illegal_all = 0, r_illegal;
+
+ while ((uc = getu8c(&s, &r_illegal)))
+ {
+ if (i < (n - 1))
+ {
+ des[i++] = uc;
+ r_illegal_all += r_illegal;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ des[i] = 0;
+ if (illegal)
+ {
+ *illegal = r_illegal_all + r_illegal;
+ }
+
+ return i;
+ }
+
+# define IF_CAN_HOLD(left, n) do { \
+ size_t m = (size_t)(n); \
+ if ((size_t)(left) < (m + 1)) return -2; \
+ (left) -= m; \
+} while (0)
+
+ int putu8c(ucs4_t uc, char **des, size_t *left)
+ {
+ if (uc < 0)
+ return -1;
+
+ if (uc < (0x1 << 7))
+ {
+ IF_CAN_HOLD(*left, 1);
+
+ **des = (char)uc;
+ *des += 1;
+ **des = 0;
+
+ return 1;
+ }
+
+ int byte_num;
+
+ if (uc < (0x1 << 11))
+ {
+ byte_num = 2;
+ }
+ else if (uc < (0x1 << 16))
+ {
+ byte_num = 3;
+ }
+ else if (uc < (0x1 << 21))
+ {
+ byte_num = 4;
+ }
+ else if (uc < (0x1 << 26))
+ {
+ byte_num = 5;
+ }
+ else
+ {
+ byte_num = 6;
+ }
+
+ IF_CAN_HOLD(*left, byte_num);
+
+ int i;
+ for (i = byte_num - 1; i > 0; --i)
+ {
+ *(uint8_t *)(*des + i) = (uc & 0x3f) | 0x80;
+ uc >>= 6;
+ }
+
+ *(uint8_t *)(*des) = uc | (0xff << (8 - byte_num));
+
+ *des += byte_num;
+ **des = 0;
+
+ return byte_num;
+ }
+
+ size_t u8encode(ucs4_t *us, char *des, size_t n, int *illegal)
+ {
+ if (n == 0)
+ return 0;
+
+ char *s = des;
+ size_t left = n;
+ size_t len = 0;
+ int r_illegal = 0;
+
+ *s = 0;
+ while (*us)
+ {
+ int ret = putu8c(*us, &s, &left);
+ if (ret > 0)
+ {
+ len += ret;
+ }
+ else if (ret == -1)
+ {
+ r_illegal += 1;
+ }
+ else
+ {
+ break;
+ }
+
+ ++us;
+ }
+
+ if (illegal)
+ {
+ *illegal = r_illegal;
+ }
+
+ return len;
+ }
+
+ /* 鍏ㄨ瀛楃 */
+ int isufullwidth(ucs4_t uc)
+ {
+ if (uc == 0x3000)
+ return 1;
+
+ if (uc >= 0xff01 && uc <= 0xff5e)
+ return 1;
+
+ return 0;
+ }
+
+ /* 鍏ㄨ瀛楁瘝 */
+ int isufullwidthalpha(ucs4_t uc)
+ {
+ if (uc >= 0xff21 && uc <= 0xff3a)
+ return 1;
+
+ if (uc >= 0xff41 && uc <= 0xff5a)
+ return 2;
+
+ return 0;
+ }
+
+ /* 鍏ㄨ鏁板瓧 */
+ int isufullwidthdigit(ucs4_t uc)
+ {
+ if (uc >= 0xff10 && uc <= 0xff19)
+ return 1;
+
+ return 0;
+ }
+
+ /* 鍏ㄨ杞崐瑙 */
+ ucs4_t ufull2half(ucs4_t uc)
+ {
+ if (uc == 0x3000)
+ return ' ';
+
+ if (uc >= 0xff01 && uc <= 0xff5e)
+ return uc - 0xfee0;
+
+ return uc;
+ }
+
+ /* 鍗婅杞叏瑙 */
+ ucs4_t uhalf2full(ucs4_t uc)
+ {
+ if (uc == ' ')
+ return 0x3000;
+
+ if (uc >= 0x21 && uc <= 0x7e)
+ return uc + 0xfee0;
+
+ return uc;
+ }
+
+ /* 涓棩闊╄秺缁熶竴琛ㄦ剰鏂囧瓧 */
+ int isuchiness(ucs4_t uc)
+ {
+ /* 鏈鍒濇湡缁熶竴姹夊瓧 */
+ if (uc >= 0x4e00 && uc <= 0x9fcc)
+ return 1;
+
+ /* 鎵╁睍 A 鍖 */
+ if (uc >= 0x3400 && uc <= 0x4db5)
+ return 2;
+
+ /* 鎵╁睍 B 鍖 */
+ if (uc >= 0x20000 && uc <= 0x2a6d6)
+ return 3;
+
+ /* 鎵╁睍 C 鍖 */
+ if (uc >= 0x2a700 && uc <= 0x2b734)
+ return 4;
+
+ /* 鎵╁睍 D 鍖 */
+ if (uc >= 0x2b740 && uc <= 0x2b81f)
+ return 5;
+
+ /* 鎵╁睍 E 鍖 */
+ if (uc >= 0x2b820 && uc <= 0x2f7ff)
+ return 6;
+
+ /* 鍙版咕鍏煎姹夊瓧 */
+ if (uc >= 0x2f800 && uc <= 0x2fa1d)
+ return 7;
+
+ /* 鍖楁湞椴滃吋瀹规眽瀛 */
+ if (uc >= 0xfa70 && uc <= 0xfad9)
+ return 8;
+
+ /* 鍏煎姹夊瓧 */
+ if (uc >= 0xf900 && uc <= 0xfa2d)
+ return 9;
+
+ /* 鍏煎姹夊瓧 */
+ if (uc >= 0xfa30 && uc <= 0xfa6d)
+ return 10;
+
+ return 0;
+ }
+
+ /* 涓枃鏍囩偣 */
+ int isuzhpunct(ucs4_t uc)
+ {
+ if (uc >= 0x3001 && uc <= 0x3002)
+ return 1;
+
+ if (uc >= 0x3008 && uc <= 0x300f)
+ return 1;
+
+ if (uc >= 0xff01 && uc <= 0xff0f)
+ return 1;
+
+ if (uc >= 0xff1a && uc <= 0xff20)
+ return 1;
+
+ if (uc >= 0xff3b && uc <= 0xff40)
+ return 1;
+
+ if (uc >= 0xff5b && uc <= 0xff5e)
+ return 1;
+
+ if (uc >= 0x2012 && uc <= 0x201f)
+ return 1;
+
+ if (uc >= 0xfe41 && uc <= 0xfe44)
+ return 1;
+
+ if (uc >= 0xfe49 && uc <= 0xfe4f)
+ return 1;
+
+ if (uc >= 0x3010 && uc <= 0x3017)
+ return 1;
+
+ return 0;
+ }
+
+ /* 鏃ユ枃骞冲亣鍚 */
+ int isuhiragana(ucs4_t uc)
+ {
+ if (uc >= 0x3040 && uc <= 0x309f)
+ return 1;
+
+ return 0;
+ }
+
+ /* 鏃ユ枃鐗囧亣鍚 */
+ int isukatakana(ucs4_t uc)
+ {
+ if (uc >= 0x30a0 && uc <= 0x30ff)
+ return 1;
+
+ if (uc >= 0x31f0 && uc <= 0x31ff)
+ return 2;
+
+ return 0;
+ }
+
+ /* 闊╂枃 */
+ int isukorean(ucs4_t uc)
+ {
+ /* 闊╂枃鎷奸煶 */
+ if (uc >= 0xac00 && uc <= 0xd7af)
+ return 1;
+
+ /* 闊╂枃瀛楁瘝 */
+ if (uc >= 0x1100 && uc <= 0x11ff)
+ return 2;
+
+ /* 闊╂枃鍏煎瀛楁瘝 */
+ if (uc >= 0x3130 && uc <= 0x318f)
+ return 3;
+
+ return 0;
+ }
+
+}
diff --git a/Client/Source/GUI/utf8.h b/Client/Source/GUI/utf8.h
new file mode 100644
index 0000000..b561b44
--- /dev/null
+++ b/Client/Source/GUI/utf8.h
@@ -0,0 +1,113 @@
+/*
+ * Description: UTF-8 瀛楃涓茬殑瑙g爜鍜岀紪鐮佸嚱鏁
+ * unicode 瀛楃澶勭悊鍑芥暟
+ * History: yang@haipo.me, 2013/05/29, create
+ *
+ * This code is in the public domain.
+ * You may use this code any way you wish, private, educational,
+ * or commercial. It's free.
+ */
+
+# pragma once
+
+# include <stdint.h>
+# include <stddef.h>
+
+namespace utf8
+{
+
+
+ /*
+ * 鏍囧噯 C 骞舵病鏈夎瀹 wchar_t 鐨勪綅鏁般備絾 GNU C Lib 淇濊瘉 wchar_t 鏄 32 浣嶇殑锛
+ * 鎵浠ュ彲浠ョ敤 wchar.h 涓畾涔夌殑鍑芥暟鏉ュ儚 wchar_t 涓鏍锋搷绾 ucs4_t.
+ * http://www.gnu.org/software/libc/manual/html_node/Extended-Char-Intro.html
+ */
+ typedef int32_t ucs4_t;
+
+ /*
+ * 浠 UTF-8 缂栫爜鐨勫瓧绗︿覆 *src 涓鍙栦竴涓 unicode 瀛楃锛屽苟鏇存柊 *src 鐨勫笺
+ *
+ * 濡傛灉閬囧埌闈炴硶 UTF-8 缂栫爜锛屽垯璺宠繃闈炴硶閮ㄥ垎銆
+ * 濡傛灉 illegal 鍙傛暟涓嶄负 NULL, 鍒 *illegal 琛ㄧず闈炴硶 UTF-8 缂栫爜瀛楄妭鏁般
+ */
+ ucs4_t getu8c(char **src, int *illegal);
+
+ /*
+ * 灏 src 鎸囧悜鐨 UTF-8 缂栫爜瀛楃涓茶В鐮佷负 unicode锛屾斁鍦ㄩ暱搴︿负 n 鐨勬暟缁 des 涓紝
+ * 骞跺湪鏈熬娣诲姞 0. 濡傛灉 des 涓嶈冻浠ュ瓨鏀炬墍鏈夌殑瀛楃锛屽垯鏈澶氫繚瀛 n - 1 涓 unicode
+ * 瀛楃骞惰ˉ 0.
+ *
+ * 濡傛灉閬囧埌闈炴硶 UTF-8 缂栫爜锛屽垯璺宠繃闈炴硶閮ㄥ垎銆
+ * 濡傛灉 illegal 涓嶄负 NULL, 鍒 *illegal 琛ㄧず闈炴硶 UTF-8 缂栫爜鐨勫瓧鑺傛暟銆
+ */
+ size_t u8decode(char const *str, ucs4_t *des, size_t n, int *illegal);
+
+ /*
+ * 灏 unicode 瀛楃 uc 缂栫爜涓 UTF-8 缂栫爜锛屾斁鍒伴暱搴︿负 *left 鐨勫瓧绗︿覆 *des 涓
+ *
+ * 濡傛灉 *des 涓嶈冻浠ュ瓨鏀 uc 瀵瑰簲鐨 UTF-8 瀛楃涓诧紝杩斿洖涓涓礋鍊笺
+ * 濡傛灉鎴愬姛锛屾洿鏂 *des 鍜 *left 鐨勫笺
+ */
+ int putu8c(ucs4_t uc, char **des, size_t *left);
+
+ /*
+ * 灏嗕互 0 缁撳熬鐨 unicode 鏁扮粍 us 缂栫爜涓 UTF-8 瀛楃涓诧紝鏀惧埌闀垮害涓 n 鐨勫瓧绗︿覆 des 涓
+ *
+ * 璐熸暟涓洪潪娉曠殑 unicode 瀛楃銆
+ * 濡傛灉 illegal 涓嶄负 NULL锛屽垯 *illegal 琛ㄧず闈炴硶鐨 unicode 瀛楃鏁般
+ */
+ size_t u8encode(ucs4_t *us, char *des, size_t n, int *illegal);
+
+ /*
+ * 鍒ゆ柇鏄惁涓哄叏瑙掑瓧绗
+ */
+ int isufullwidth(ucs4_t uc);
+
+ /*
+ * 鍒ゆ柇鏄惁涓哄叏瑙掑瓧姣
+ */
+ int isufullwidthalpha(ucs4_t uc);
+
+ /*
+ * 鍒ゆ柇鏄惁涓哄叏瑙掓暟瀛
+ */
+ int isufullwidthdigit(ucs4_t uc);
+
+ /*
+ * 鍏ㄨ瀛楃杞崐瑙掑瓧绗︺
+ * 濡傛灉 uc 涓哄叏瑙掑瓧绗︼紝鍒欒繑鍥炲搴旂殑鍗婅瀛楃锛屽惁鍒欒繑鍥 uc 鏈韩銆
+ */
+ ucs4_t ufull2half(ucs4_t uc);
+
+ /*
+ * 鍗婅瀛楃杞叏瑙掑瓧绗
+ * 濡傛灉 uc 涓哄崐瑙掑瓧绗︼紝鍒欒繑鍥炲搴旂殑鍏ㄨ瀛楃锛屽惁鍒欒繑鍥 uc 鏈韩銆
+ */
+ ucs4_t uhalf2full(ucs4_t uc);
+
+ /*
+ * 鍒ゆ柇鏄惁涓烘眽瀛楀瓧绗︼紙涓棩闊╄秺缁熶竴琛ㄦ剰鏂囧瓧锛
+ */
+ int isuchiness(ucs4_t uc);
+
+ /*
+ * 鍒ゆ柇鏄惁涓轰腑鏂囨爣鐐
+ */
+ int isuzhpunct(ucs4_t uc);
+
+ /*
+ * 鍒ゆ柇鏄惁涓烘棩鏂囧钩鍋囧悕瀛楃
+ */
+ int isuhiragana(ucs4_t uc);
+
+ /*
+ * 鍒ゆ柇鏄惁涓烘棩鏂囩墖鍋囧悕瀛楃
+ */
+ int isukatakana(ucs4_t uc);
+
+ /*
+ * 鍒ゆ柇鏄惁涓洪煩鏂囧瓧绗
+ */
+ int isukorean(ucs4_t uc);
+
+}