From c9522b52967159935c9cd486c089e4039c38c364 Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Thu, 15 Jun 2023 20:48:30 -0700
Subject: [PATCH] UI Label Wrap

---
 src/dawn/scene/components/ui/text/UILabel.cpp | 306 ++++++++++--------
 src/dawn/scene/components/ui/text/UILabel.hpp |  25 +-
 src/dawnliminal/scenes/HelloWorldScene.hpp    |   6 +-
 3 files changed, 173 insertions(+), 164 deletions(-)

diff --git a/src/dawn/scene/components/ui/text/UILabel.cpp b/src/dawn/scene/components/ui/text/UILabel.cpp
index 7f97a535..5967b71e 100644
--- a/src/dawn/scene/components/ui/text/UILabel.cpp
+++ b/src/dawn/scene/components/ui/text/UILabel.cpp
@@ -15,7 +15,13 @@ UILabel::UILabel(SceneItem *item) :
 }
 
 void UILabel::onStart() {
+  UIComponentRenderable::onStart();
+
   this->shaderBuffer.init();
+
+  useEvent([&]{
+    this->rebufferQuads(this->texts);
+  }, eventAlignmentUpdated);
 }
 
 std::vector<struct ShaderPassItem> UILabel::getUIRenderPasses() {
@@ -68,163 +74,179 @@ std::vector<struct ShaderPassItem> UILabel::getUIRenderPasses() {
 }
 
 float_t UILabel::getContentWidth() {
-  return 0;
+  return 1;
 }
 
 float_t UILabel::getContentHeight() {
-  return 0;
+  return 1;
 }
 
-void UILabel::rebufferQuads(std::vector<struct UILabelText> newTexts) {
-  auto oldTexts = this->texts;
-
-  textureMap.clear();
-  glm::vec2 position(0, 0);
-  struct FontShaderBufferData fontData;
-  int32_t quadIndex = 0;
-  int32_t partIndex = 0;
-  quadCountTotal = 0;
+void UILabel::rebufferQuads(const std::vector<struct UILabelText> newTexts) {
   int32_t nextTexture = 0;
-
-  // Determine how many quads there are, and the texture indexes.
+  glm::vec2 position(0, 0);
+  int32_t partIndex = 0;
+  std::vector<std::pair<glm::vec4, glm::vec4>> vertices;
+  struct FontShaderBufferData fontData;
+  quadCountTotal = 0;
+  std::vector<struct UILabelText> realNewTexts;
+  
+  // Determine font dimensions.
   auto itText = newTexts.begin();
   while(itText != newTexts.end()) {
-    quadCountTotal += itText->text.length();
-
-    // Determine font and lock it.
-    assertNotNull(itText->style.font);
-    itText->lockId = itText->style.font->lock(NewTrueTypeFaceTextureStyle{
-      itText->style.size,
-      itText->style.style
-    });
-    assertTrue(itText->lockId != -1);
-    itText->texture = itText->style.font->getTexture(itText->lockId);
-
-    // Check for existing texture, if not, map it.
-    if(textureMap.find(itText->texture) == textureMap.end()) {
-      assertTrue(nextTexture < FONT_SHADER_TEXTURE_MAX);
-      textureMap[itText->texture] = nextTexture++;
-    }
-
+    position.y = mathMax<float_t>(position.y, itText->style.size);
     ++itText;
   }
 
-  // Cleanup old texst, we do this second so we don't unlock, cleanup, and then
-  // lock the same font, causing it to have to re-load.
-  itText = oldTexts.begin();
-  while(itText != oldTexts.end()) {
+  // Now generate quads
+  itText = newTexts.begin();
+  while(itText != newTexts.end()) {
+    auto text = *itText;
+    struct UILabelText realText;
+
+    // Clone values
+    realText.style = text.style;
+
+    // Lock the font
+    assertNotNull(text.style.font);
+    realText.lockId = text.style.font->lock(NewTrueTypeFaceTextureStyle{
+      text.style.size,
+      text.style.style
+    });
+    assertTrue(realText.lockId != -1);
+    realText.texture = text.style.font->getTexture(realText.lockId);
+
+    // Map texture
+    if(textureMap.find(realText.texture) == textureMap.end()) {
+      assertTrue(nextTexture < FONT_SHADER_TEXTURE_MAX);
+      textureMap[realText.texture] = nextTexture++;
+    }
+
+    // Buffer shader values
+    fontData.textures[partIndex] = textureMap[realText.texture];
+    fontData.colors[partIndex] = realText.style.color;
+
+    // Get some texture info
+    glm::vec2 wh = glm::vec2(
+      realText.texture->texture.getWidth(),
+      realText.texture->texture.getHeight()
+    );
+
+    // Now, iterate each character
+    auto len = text.text.length();
+    int32_t lastSpaceCharacter = -1;
+    for(int32_t i = 0; i < len; i++) {
+      char ch = text.text[i];
+
+      // Handle newline
+      if(ch == '\n') {
+        position.x = 0;
+        position.y += realText.style.size;
+
+        glm::vec4 uvs(0, 0, 1, 1);
+        glm::vec4 vert(0, 0, 0, 0);
+        vertices.push_back(std::make_pair(vert, uvs));
+        fontData.fontQuadMappings[quadCountTotal] = partIndex;
+        quadCountTotal++;
+        realText.text += ch;
+        lastSpaceCharacter = i;
+        continue;
+      } else if(ch == ' ') {
+        lastSpaceCharacter = i;
+      }
+
+      // Validate characters
+      assertTrue(ch >= NEW_TRUETYPE_CHAR_BEGIN && ch < NEW_TRUETYPE_CHAR_END);
+      assertTrue(ch != '\r');
+      assertTrue(ch != '\t');
+      assertTrue(ch != '\n');
+
+      FT_ULong c = ch;
+      auto charInfo = realText.texture->getCharacterData(c);
+
+      // Word wrapping
+      if(
+        ch != ' ' &&
+        lastSpaceCharacter != -1 &&
+        this->width > charInfo.bitmapSize.x &&
+        (position.x + charInfo.advanceX) > this->width
+      ) {
+        int32_t diff = i - lastSpaceCharacter;
+        for(int32_t k = 0; k < diff; k++) vertices.pop_back();
+        text.text[lastSpaceCharacter] = '\n';
+        i = lastSpaceCharacter - 1;
+        lastSpaceCharacter = -1;
+        quadCountTotal -= diff;
+        continue;
+      }
+
+      // Buffer coordinates
+      glm::vec4 uvs;
+      uvs.x = 0.0f;
+      uvs.y = charInfo.textureY / wh.y;
+      uvs.w = charInfo.bitmapSize.x / wh.x;
+      uvs.z = uvs.y + (charInfo.bitmapSize.y / wh.y);
+      
+      glm::vec4 vert;
+      vert.x = position.x + charInfo.bitmapPosition.x;
+      vert.y = position.y + charInfo.bitmapPosition.y;
+      vert.w = vert.x + charInfo.bitmapSize.x;
+      vert.z = vert.y + charInfo.bitmapSize.y;
+
+      // TODO:Check wordwrap here
+      vertices.push_back(std::make_pair(vert, uvs));
+
+      // Move the current position along.
+      position.x += charInfo.advanceX;
+      position.y += charInfo.advanceY;
+
+      // Set the part index to the quad mappings
+      fontData.fontQuadMappings[quadCountTotal] = partIndex;
+      quadCountTotal++;
+      realText.text += ch;
+    }
+
+    // Next
+    ++partIndex;
+    ++itText;
+    realNewTexts.push_back(realText);
+  }
+
+  // Create mesh
+  this->mesh.createBuffers(
+    QUAD_VERTICE_COUNT * vertices.size(),
+    QUAD_INDICE_COUNT * vertices.size()
+  );
+
+  // Now buffer the quads.
+  int32_t j = 0;
+  auto itQuad = vertices.begin();
+  while(itQuad != vertices.end()) {
+    auto vert = itQuad->first;
+    auto uvs = itQuad->second;
+
+    QuadMesh::bufferQuadMeshWithZ(&this->mesh,
+      glm::vec2(vert.x, vert.y), glm::vec2(uvs.x, uvs.y),
+      glm::vec2(vert.w, vert.z), glm::vec2(uvs.w, uvs.z),
+      0.0f,
+      j * QUAD_VERTICE_COUNT, j * QUAD_INDICE_COUNT
+    );
+    j++;
+    ++itQuad;
+  }
+
+  // Buffer data
+  shaderBuffer.buffer(&fontData);
+
+  // Finally, release the old locks
+  itText = textsBuffered.begin();
+  while(itText != textsBuffered.end()) {
     assertTrue(itText->lockId != -1);
     assertNotNull(itText->style.font);
     itText->style.font->unlock(itText->lockId);
     ++itText;
   }
 
-  // Update texts.
-  this->texts = newTexts;
-  
-  // Create mesh
-  this->mesh.createBuffers(
-    QUAD_VERTICE_COUNT * quadCountTotal,
-    QUAD_INDICE_COUNT * quadCountTotal
-  );
-
-  // Buffer the text quads
-  itText = newTexts.begin();
-  while(itText != newTexts.end()) {
-    position.y += itText->style.size;
-    quadIndex += this->bufferQuads(
-      *itText,
-      fontData,
-      textureMap,
-      position,
-      quadIndex,
-      partIndex
-    );
-    // position.y -= itText->style.size;
-    ++partIndex;
-    ++itText;
-  }
-
-  shaderBuffer.buffer(&fontData);
-}
-
-int32_t UILabel::bufferQuads(
-  struct UILabelText text,
-  struct FontShaderBufferData &bufferData,
-  std::map<NewTrueTypeFaceTexture*, int32_t> &textureMap,
-  glm::vec2 &position,
-  int32_t quadStart,
-  int32_t partIndex
-) {
-  // Get string length
-  int32_t len = text.text.length();
-  
-  glm::vec2 wh = glm::vec2(
-    text.texture->texture.getWidth(),
-    text.texture->texture.getHeight()
-  );
-
-  // For each char
-  int32_t lastSpaceCharacter = -1;
-  for(int32_t i = 0; i < len; i++) {
-    char ch = text.text[i];
-
-    if(ch == '\n') {
-      position.x = 0;
-      position.y += text.style.size;
-      ch = ' ';
-      lastSpaceCharacter = i;
-    } else if(ch == ' ') {
-      lastSpaceCharacter = i;
-    }
-    
-    // Invalid/Unsupported chars
-    assertTrue(ch >= NEW_TRUETYPE_CHAR_BEGIN && ch < NEW_TRUETYPE_CHAR_END);
-    assertTrue(ch != '\r');
-    assertTrue(ch != '\t');
-
-    int32_t j = quadStart + i;
-    FT_ULong c = ch;
-    auto charInfo = text.texture->getCharacterData(c);
-
-    // Word wrapping
-    if(
-      lastSpaceCharacter != -1 &&
-      this->width > 0 &&
-      (position.x+charInfo.advanceX) > this->width
-    ) {
-      text.text[lastSpaceCharacter] = '\n';
-      i = lastSpaceCharacter - 1;
-      lastSpaceCharacter = -1;
-      continue;
-    }
-
-    // Determine texture coordinates.
-    glm::vec2 uv0 = glm::vec2(0.0f, charInfo.textureY) / wh;
-    glm::vec2 uv1 = uv0 + (charInfo.bitmapSize / wh);
-
-    // Buffer the quad.
-    assertTrue(j < FONT_SHADER_QUADS_MAX);
-    QuadMesh::bufferQuadMeshWithZ(&this->mesh,
-      position + charInfo.bitmapPosition, uv0,
-      position + charInfo.bitmapPosition + charInfo.bitmapSize, uv1,
-      0.0f,
-      j * QUAD_VERTICE_COUNT, j * QUAD_INDICE_COUNT
-    );
-
-    // Move the current position along.
-    position.x += charInfo.advanceX;
-    position.y += charInfo.advanceY;
-
-    // Set the part index to the quad mappings
-    bufferData.fontQuadMappings[j] = partIndex;
-  }
-
-  // Map texture level values
-  auto textureId = textureMap.find(text.texture);
-  assertTrue(textureId != textureMap.end());
-  bufferData.textures[partIndex] = textureId->second;
-  bufferData.colors[partIndex] = text.style.color;
-
-  return len;
+  // Update
+  textsBuffered = realNewTexts;
+  texts = newTexts;
 }
\ No newline at end of file
diff --git a/src/dawn/scene/components/ui/text/UILabel.hpp b/src/dawn/scene/components/ui/text/UILabel.hpp
index d1edd548..9e015d02 100644
--- a/src/dawn/scene/components/ui/text/UILabel.hpp
+++ b/src/dawn/scene/components/ui/text/UILabel.hpp
@@ -22,6 +22,9 @@ namespace Dawn {
   struct UILabelText {
     std::string text;
     struct UILabelStyle style;
+    
+    glm::vec2 position;
+    glm::vec2 size;
 
     // Part index?
     // Quad start?
@@ -39,29 +42,9 @@ namespace Dawn {
       Mesh mesh;
       FontShaderBuffer shaderBuffer;
       std::vector<struct UILabelText> texts;
+      std::vector<struct UILabelText> textsBuffered;
       std::map<NewTrueTypeFaceTexture*, int32_t> textureMap;
 
-      /**
-       * Buffers the quads for the given text and updates the progressing values
-       * as the buffer process continues.
-       * 
-       * @param text Text information to buffer.
-       * @param bufferData The output quad mappings for the text.
-       * @param textureMap Texture map for the textures to map.
-       * @param position The 2D position to buffer the quads at.
-       * @param quadStart The starting quad index.
-       * @param partIndex The part index to store for each quad buffered.
-       * @return The number of quads buffered, not the string length.
-       */
-      int32_t bufferQuads(
-        struct UILabelText text,
-        struct FontShaderBufferData &bufferData,
-        std::map<NewTrueTypeFaceTexture*, int32_t> &textureMap,
-        glm::vec2 &position,
-        int32_t quadStart,
-        int32_t partIndex
-      );
-
     public:
       int32_t quadStart = 0;
       int32_t quadCount = -1;
diff --git a/src/dawnliminal/scenes/HelloWorldScene.hpp b/src/dawnliminal/scenes/HelloWorldScene.hpp
index 7b1f46e1..24f6e209 100644
--- a/src/dawnliminal/scenes/HelloWorldScene.hpp
+++ b/src/dawnliminal/scenes/HelloWorldScene.hpp
@@ -23,10 +23,13 @@ namespace Dawn {
 
         auto canvasItem = this->createSceneItem();
         auto canvas = canvasItem->addComponent<UICanvas>();
+        canvas->camera = camera;
 
         auto newLabelItem = this->createSceneItem();
-        newLabelItem->transform.setParent(canvas->transform);
         auto newLabel = newLabelItem->addComponent<UIRichTextLabel>();
+        newLabel->alignment = glm::vec4(0, 0, 0, 0);
+        newLabel->alignX = UI_COMPONENT_ALIGN_STRETCH;
+        newLabel->alignY = UI_COMPONENT_ALIGN_STRETCH;
         // newLabel->maxWidth = 300.0f;
         newLabel->richText = std::string(
           "<font font=\"font_arial\" size=\"16\" color=\"COLOR_BLUE\">"
@@ -44,6 +47,7 @@ namespace Dawn {
             "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
           "</font>"
         );
+        newLabelItem->transform.setParent(canvas->transform);
       }
       
       std::vector<Asset*> getRequiredAssets() override {