diff --git a/src/dawn/games/vn/components/VNTextboxScroller.hpp b/src/dawn/games/vn/components/VNTextboxScroller.hpp index f22c0022..d014bb2a 100644 --- a/src/dawn/games/vn/components/VNTextboxScroller.hpp +++ b/src/dawn/games/vn/components/VNTextboxScroller.hpp @@ -5,7 +5,7 @@ #pragma once #include "scene/SceneItemComponent.hpp" -#include "scene/components/ui/text/UILabelNew.hpp" +#include "scene/components/ui/text/UILabel.hpp" #include "input/InputManager.hpp" #define VN_TEXTBOX_SPEED 25.0f @@ -15,7 +15,7 @@ namespace Dawn { class VNTextboxScroller : public SceneItemComponent { public: // @optional - StateProperty label; + StateProperty label; StateEvent<> eventReadyToClose; StateEvent<> eventCharacterRevealed; diff --git a/src/dawn/scene/components/debug/FPSLabelComponent.hpp b/src/dawn/scene/components/debug/FPSLabelComponent.hpp index 7b6f39c9..2a6c7f5f 100644 --- a/src/dawn/scene/components/debug/FPSLabelComponent.hpp +++ b/src/dawn/scene/components/debug/FPSLabelComponent.hpp @@ -4,13 +4,13 @@ // https://opensource.org/licenses/MIT #pragma once -#include "scene/components/ui/text/UILabelNew.hpp" +#include "scene/components/ui/text/UILabel.hpp" namespace Dawn { class FPSLabelComponent : public SceneItemComponent { public: /* @optional */ - UILabelNew *label = nullptr; + UILabel *label = nullptr; FPSLabelComponent(SceneItem *item); void onStart() override; diff --git a/src/dawn/scene/components/ui/UIComponent.hpp b/src/dawn/scene/components/ui/UIComponent.hpp index 6e5a88e6..d5560815 100644 --- a/src/dawn/scene/components/ui/UIComponent.hpp +++ b/src/dawn/scene/components/ui/UIComponent.hpp @@ -40,7 +40,7 @@ namespace Dawn { /** * Internal method to update the alignment of this item. */ - void updateAlignment(); + virtual void updateAlignment(); public: StateProperty alignmentNeedsUpdating; diff --git a/src/dawn/scene/components/ui/text/CMakeLists.txt b/src/dawn/scene/components/ui/text/CMakeLists.txt index cb2833fc..45c2a0b5 100644 --- a/src/dawn/scene/components/ui/text/CMakeLists.txt +++ b/src/dawn/scene/components/ui/text/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2022 Dominic Masters +# Copyright (c) 2023 Dominic Masters # # This software is released under the MIT License. # https://opensource.org/licenses/MIT @@ -6,5 +6,6 @@ # Sources target_sources(${DAWN_TARGET_NAME} PRIVATE - UILabelNew.cpp + UILabel.cpp + UIRichTextLabel.cpp ) \ No newline at end of file diff --git a/src/dawn/scene/components/ui/text/UILabelNew.cpp b/src/dawn/scene/components/ui/text/UILabel.cpp similarity index 61% rename from src/dawn/scene/components/ui/text/UILabelNew.cpp rename to src/dawn/scene/components/ui/text/UILabel.cpp index 72a0d8ee..7f97a535 100644 --- a/src/dawn/scene/components/ui/text/UILabelNew.cpp +++ b/src/dawn/scene/components/ui/text/UILabel.cpp @@ -3,83 +3,22 @@ // This software is released under the MIT License. // https://opensource.org/licenses/MIT -#include "UILabelNew.hpp" +#include "UILabel.hpp" #include "game/DawnGame.hpp" using namespace Dawn; -UILabelNew::UILabelNew(SceneItem *item) : +UILabel::UILabel(SceneItem *item) : UIComponentRenderable(item) { } -void UILabelNew::onStart() { +void UILabel::onStart() { this->shaderBuffer.init(); - - std::vector styleStack; - struct UILabelStyle current; - styleStack.push_back(current); - std::vector texts; - - std::function parseChildren = [&](Xml *node) { - if(node->children.empty()) { - struct UILabelText text; - text.style = current; - text.text = node->value; - texts.push_back(text); - } else { - auto itNode = node->children.begin(); - while(itNode != node->children.end()) { - auto child = *itNode; - assertTrue(child->node == "font"); - - struct UILabelStyle style; - if(child->attributes.contains("font")) { - style.font = this->getGame()->assetManager.get(child->attributes["font"]); - } else { - style.font = current.font; - } - - if(child->attributes.contains("size")) { - style.size = std::stoi(child->attributes["size"]); - } else { - style.size = current.size; - } - - if(child->attributes.contains("style")) { - std::string s = child->attributes["style"]; - style.style = 0; - if(s.find("bold") != std::string::npos) style.style |= NEW_TRUETYPE_VARIANT_BOLD; - if(s.find("italic") != std::string::npos) style.style |= NEW_TRUETYPE_VARIANT_ITALICS; - } else { - style.style = current.style; - } - - if(child->attributes.contains("color")) { - style.color = Color::fromString(child->attributes["color"]); - } else { - style.color = current.color; - } - - styleStack.push_back(style); - current = style; - - parseChildren(child); - - styleStack.pop_back(); - current = styleStack.back(); - ++itNode; - } - } - }; - - auto root = Xml::load("" + this->test + ""); - parseChildren(&root); - this->rebufferQuads(texts); } -std::vector UILabelNew::getUIRenderPasses() { +std::vector UILabel::getUIRenderPasses() { // if(this->texts.size() == 0) return {}; auto canvas = this->getCanvas(); @@ -92,6 +31,8 @@ std::vector UILabelNew::getUIRenderPasses() { item.parameterBuffers[shader->bufferUiCanvas] = &canvas->shaderBuffer; item.parameterBuffers[shader->bufferFont] = &this->shaderBuffer; item.renderFlags = RENDER_MANAGER_RENDER_FLAG_BLEND; + item.start = quadStart * QUAD_VERTICE_COUNT; + item.count = quadCount == -1 ? -1 : quadCount * QUAD_VERTICE_COUNT; // Map texture slots auto it = textureMap.begin(); @@ -126,23 +67,15 @@ std::vector UILabelNew::getUIRenderPasses() { return { item }; } -float_t UILabelNew::getWidth() { +float_t UILabel::getContentWidth() { return 0; } -float_t UILabelNew::getHeight() { +float_t UILabel::getContentHeight() { return 0; } -float_t UILabelNew::getContentWidth() { - return 0; -} - -float_t UILabelNew::getContentHeight() { - return 0; -} - -void UILabelNew::rebufferQuads(std::vector texts) { +void UILabel::rebufferQuads(std::vector newTexts) { auto oldTexts = this->texts; textureMap.clear(); @@ -150,13 +83,13 @@ void UILabelNew::rebufferQuads(std::vector texts) { struct FontShaderBufferData fontData; int32_t quadIndex = 0; int32_t partIndex = 0; - int32_t quadCount = 0; + quadCountTotal = 0; int32_t nextTexture = 0; // Determine how many quads there are, and the texture indexes. - auto itText = texts.begin(); - while(itText != texts.end()) { - quadCount += itText->text.length(); + auto itText = newTexts.begin(); + while(itText != newTexts.end()) { + quadCountTotal += itText->text.length(); // Determine font and lock it. assertNotNull(itText->style.font); @@ -172,9 +105,6 @@ void UILabelNew::rebufferQuads(std::vector texts) { assertTrue(nextTexture < FONT_SHADER_TEXTURE_MAX); textureMap[itText->texture] = nextTexture++; } - - // Set initial line height - position.y = mathMax(itText->style.size, position.y); ++itText; } @@ -190,17 +120,18 @@ void UILabelNew::rebufferQuads(std::vector texts) { } // Update texts. - this->texts = texts; + this->texts = newTexts; // Create mesh this->mesh.createBuffers( - QUAD_VERTICE_COUNT * quadCount, - QUAD_INDICE_COUNT * quadCount + QUAD_VERTICE_COUNT * quadCountTotal, + QUAD_INDICE_COUNT * quadCountTotal ); // Buffer the text quads - itText = texts.begin(); - while(itText != texts.end()) { + itText = newTexts.begin(); + while(itText != newTexts.end()) { + position.y += itText->style.size; quadIndex += this->bufferQuads( *itText, fontData, @@ -209,6 +140,7 @@ void UILabelNew::rebufferQuads(std::vector texts) { quadIndex, partIndex ); + // position.y -= itText->style.size; ++partIndex; ++itText; } @@ -216,7 +148,7 @@ void UILabelNew::rebufferQuads(std::vector texts) { shaderBuffer.buffer(&fontData); } -int32_t UILabelNew::bufferQuads( +int32_t UILabel::bufferQuads( struct UILabelText text, struct FontShaderBufferData &bufferData, std::map &textureMap, @@ -233,17 +165,46 @@ int32_t UILabelNew::bufferQuads( ); // 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, diff --git a/src/dawn/scene/components/ui/text/UILabelNew.hpp b/src/dawn/scene/components/ui/text/UILabel.hpp similarity index 82% rename from src/dawn/scene/components/ui/text/UILabelNew.hpp rename to src/dawn/scene/components/ui/text/UILabel.hpp index fe679b51..d1edd548 100644 --- a/src/dawn/scene/components/ui/text/UILabelNew.hpp +++ b/src/dawn/scene/components/ui/text/UILabel.hpp @@ -9,6 +9,8 @@ #include "asset/assets/NewTrueTypeAsset.hpp" #include "util/Xml.hpp" +#define UI_LABEL_MAX_WIDTH_NONE -1 + namespace Dawn { struct UILabelStyle { struct Color color = COLOR_WHITE; @@ -32,7 +34,7 @@ namespace Dawn { struct NewTrueTypeFaceTexture *texture = nullptr; }; - class UILabelNew : public UIComponentRenderable { + class UILabel : public UIComponentRenderable { private: Mesh mesh; FontShaderBuffer shaderBuffer; @@ -60,19 +62,24 @@ namespace Dawn { int32_t partIndex ); - public: - std::string test; + int32_t quadStart = 0; + int32_t quadCount = -1; + int32_t quadCountTotal = -1; - UILabelNew(SceneItem *item); + UILabel(SceneItem *item); void onStart() override; std::vector getUIRenderPasses() override; - float_t getWidth() override; - float_t getHeight() override; float_t getContentWidth() override; float_t getContentHeight() override; + /** + * Rebuffer the quads for this label. This method will perform all the + * necessary difference calculations from where the current state of this text is. + * + * @param texts Texts to buffer. + */ void rebufferQuads(std::vector texts); }; } \ No newline at end of file diff --git a/src/dawn/scene/components/ui/text/UIRichTextLabel.cpp b/src/dawn/scene/components/ui/text/UIRichTextLabel.cpp new file mode 100644 index 00000000..2c71b31b --- /dev/null +++ b/src/dawn/scene/components/ui/text/UIRichTextLabel.cpp @@ -0,0 +1,82 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "UIRichTextLabel.hpp" +#include "game/DawnGame.hpp" + +using namespace Dawn; + +UIRichTextLabel::UIRichTextLabel(SceneItem *item) : + UILabel(item) +{ + +} + +void UIRichTextLabel::onStart() { + UILabel::onStart(); + + useEffect([&]{ + std::vector styleStack; + struct UILabelStyle current; + styleStack.push_back(current); + std::vector bufferTexts; + + std::function parseChildren = [&](Xml *node) { + if(node->children.empty()) { + struct UILabelText text; + text.style = current; + text.text = node->value; + bufferTexts.push_back(text); + } else { + auto itNode = node->children.begin(); + while(itNode != node->children.end()) { + auto child = *itNode; + assertTrue(child->node == "font"); + + struct UILabelStyle style; + if(child->attributes.contains("font")) { + style.font = this->getGame()->assetManager.get(child->attributes["font"]); + } else { + style.font = current.font; + } + + if(child->attributes.contains("size")) { + style.size = std::stoi(child->attributes["size"]); + } else { + style.size = current.size; + } + + if(child->attributes.contains("style")) { + std::string s = child->attributes["style"]; + style.style = 0; + if(s.find("bold") != std::string::npos) style.style |= NEW_TRUETYPE_VARIANT_BOLD; + if(s.find("italic") != std::string::npos) style.style |= NEW_TRUETYPE_VARIANT_ITALICS; + } else { + style.style = current.style; + } + + if(child->attributes.contains("color")) { + style.color = Color::fromString(child->attributes["color"]); + } else { + style.color = current.color; + } + + styleStack.push_back(style); + current = style; + + parseChildren(child); + + styleStack.pop_back(); + current = styleStack.back(); + ++itNode; + } + } + }; + + auto root = Xml::load("" + ((std::string)this->richText) + ""); + parseChildren(&root); + this->rebufferQuads(bufferTexts); + }, this->richText)(); +} \ No newline at end of file diff --git a/src/dawn/scene/components/ui/text/UIRichTextLabel.hpp b/src/dawn/scene/components/ui/text/UIRichTextLabel.hpp new file mode 100644 index 00000000..e473775d --- /dev/null +++ b/src/dawn/scene/components/ui/text/UIRichTextLabel.hpp @@ -0,0 +1,18 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "UILabel.hpp" + +namespace Dawn { + class UIRichTextLabel : public UILabel { + public: + StateProperty richText; + + UIRichTextLabel(SceneItem *item); + + void onStart() override; + }; +} \ No newline at end of file diff --git a/src/dawnliminal/scenes/HelloWorldScene.hpp b/src/dawnliminal/scenes/HelloWorldScene.hpp index c4179d9b..7b1f46e1 100644 --- a/src/dawnliminal/scenes/HelloWorldScene.hpp +++ b/src/dawnliminal/scenes/HelloWorldScene.hpp @@ -7,7 +7,7 @@ #include "scene/Scene.hpp" #include "prefabs/SimpleSpinningCubePrefab.hpp" #include "scene/components/display/Camera.hpp" -#include "scene/components/ui/text/UILabelNew.hpp" +#include "scene/components/ui/text/UIRichTextLabel.hpp" namespace Dawn { class HelloWorldScene : public Scene { @@ -26,8 +26,24 @@ namespace Dawn { auto newLabelItem = this->createSceneItem(); newLabelItem->transform.setParent(canvas->transform); - auto newLabel = newLabelItem->addComponent(); - newLabel->test = "HelloWorld"; + auto newLabel = newLabelItem->addComponent(); + // newLabel->maxWidth = 300.0f; + newLabel->richText = std::string( + "" + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "" + ); } std::vector getRequiredAssets() override { diff --git a/src/dawnopengl/display/shader/shaders/FontShader.hpp b/src/dawnopengl/display/shader/shaders/FontShader.hpp index 555f3a8d..429f04aa 100644 --- a/src/dawnopengl/display/shader/shaders/FontShader.hpp +++ b/src/dawnopengl/display/shader/shaders/FontShader.hpp @@ -8,7 +8,7 @@ #include "util/macro.hpp" #define FONT_SHADER_PARTS_MAX 4 -#define FONT_SHADER_QUADS_MAX 32 +#define FONT_SHADER_QUADS_MAX 1024 #define FONT_SHADER_TEXTURE_MAX 4 namespace Dawn {