diff --git a/examples/HelloText/README.md b/examples/HelloText/README.md new file mode 100644 index 0000000..6068a7e --- /dev/null +++ b/examples/HelloText/README.md @@ -0,0 +1,32 @@ +# HelloWindow Example + +## Description + +This example demonstrates how to draw pixels to a window. + +## Expected Result + +A window with a green and blue colored square, when clicking on the square it logs the coordinates relative to the square. + +## Highlighted Code Snippet + +```cpp +UiElement& element = window.elements.emplace_back( + 0.5, + 0.5, + 2, + 1, + 0.5f, + 0.5f, + 0.5, + 0.5, + 0, + false +); +``` + +## How to Run + +```bash +crafter-build build executable -r +``` \ No newline at end of file diff --git a/examples/HelloText/inter.ttf b/examples/HelloText/inter.ttf new file mode 100644 index 0000000..e31b51e Binary files /dev/null and b/examples/HelloText/inter.ttf differ diff --git a/examples/HelloText/main.cpp b/examples/HelloText/main.cpp new file mode 100644 index 0000000..fbccd78 --- /dev/null +++ b/examples/HelloText/main.cpp @@ -0,0 +1,33 @@ +import Crafter.Event; +import Crafter.Graphics; +import std; +using namespace Crafter; + +int main() { + WindowWayland window(1280, 720, "Hello Input!"); + + RenderingElement element( + { + FractionalToMapped(0), //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor + FractionalToMapped(0.5), //anchorY: relative position where this elements y anchor (top-left) is placed to its parent y anchor + FractionalToMapped(0.1), //relativeSizeX: the relative x size this element should be scaled to compared to its parent + FractionalToMapped(1), //relativeSizeY: the relative y size this element should be scaled to compared to its parent + FractionalToMapped(0), //anchorOffsetX: the amount this element's anchor should be offset from the top left corner (0.5 to in the middle) + FractionalToMapped(0), //anchorOffsetY: the amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle) + 0 //z: this elements Z position + }, + OpaqueType::FullyOpaque + ); + + Font font("inter.ttf"); + std::string text = "testtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt"; + element.UpdatePosition(window); + std::vector lines = element.ResizeText(window, text, 200, font, TextOverflowMode::Clip, TextScaleMode::Element); // anchor.width automatically scales with our text + element.RenderText(window, lines, 200, {0,0,0,255}, font); + + window.elements.push_back(&element); + + window.Render(); + window.StartSync(); +} + diff --git a/examples/HelloText/project.json b/examples/HelloText/project.json new file mode 100644 index 0000000..8542e7e --- /dev/null +++ b/examples/HelloText/project.json @@ -0,0 +1,17 @@ +{ + "name": "crafter-graphics", + "configurations": [ + { + "name": "executable", + "implementations": ["main"], + "dependencies": [ + { + "path":"../../project.json", + "configuration":"lib-wayland-debug" + } + ], + "debug": true, + "additional_files": ["inter.ttf"] + } + ] +} diff --git a/implementations/Crafter.Graphics-TextElement.cpp b/implementations/Crafter.Graphics-TextElement.cpp deleted file mode 100644 index db360f8..0000000 --- a/implementations/Crafter.Graphics-TextElement.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/* -Crafter®.Graphics -Copyright (C) 2025 Catcrafts® -Catcrafts.net - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 3.0 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -module; -#include "../lib/stb_truetype.h" -module Crafter.Graphics:TextElement_impl; -import :TextElement; -import :RenderingElement; -import :Window; -import :Types; -import :Font; -import std; - -using namespace Crafter; - -TextElement::TextElement(Anchor anchor): RenderingElement(anchor, OpaqueType::Transparent) { - -} - -void TextElement::RenderText(Window& window, const std::string_view text, float size, Pixel_BU8_GU8_RU8_AU8 color, Font& font, TextAlignment alignment, VerticalTextAlignment verticalAlignment, TextOverflowMode overflowMode) { - window.AddDirtyRect(scaled); - // Calculate the actual size needed for the text - float scale = stbtt_ScaleForPixelHeight(&font.font, size); - int baseline = (int)(font.ascent * scale); - - // Clear the scaled buffer - for (auto& pixel : buffer) { - pixel = {0, 0, 0, 0}; - } - - // If we have no text or no space, return early - if (text.empty() || scaled.width == 0 || scaled.height == 0) { - return; - } - - // Handle text overflow based on mode - if (overflowMode == TextOverflowMode::Clip) { - // We need to handle line breaks and calculate lines - std::vector lines; - std::string_view remaining = text; - - while (!remaining.empty()) { - // Find next newline or end of string - auto newlinePos = remaining.find('\n'); - if (newlinePos != std::string_view::npos) { - lines.emplace_back(remaining.substr(0, newlinePos)); - remaining = remaining.substr(newlinePos + 1); - } else { - lines.emplace_back(remaining); - break; - } - } - - // Now process each line - std::uint_fast32_t lineHeight = (font.ascent - font.descent) * scale; - std::uint_fast32_t maxLineWidth = scaled.width; - std::uint_fast32_t totalTextHeight = lines.size() * lineHeight; - - // Calculate vertical offset based on vertical alignment - std::int_fast32_t verticalOffset = 0; - switch (verticalAlignment) { - case VerticalTextAlignment::Top: - verticalOffset = 0; - break; - case VerticalTextAlignment::Middle: - verticalOffset = (scaled.height - totalTextHeight) / 2; - break; - case VerticalTextAlignment::Bottom: - verticalOffset = scaled.height - totalTextHeight; - break; - } - - std::uint_fast32_t currentLineStartY = 0; - - for (std::size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) { - std::string_view line = lines[lineIndex]; - - // Calculate line width - std::uint_fast32_t lineWidth = 0; - std::vector lineAdvances; - for (const char c : line) { - int advance, lsb; - stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb); - lineAdvances.push_back(advance); - lineWidth += (int)(advance * scale); - } - - // Determine horizontal position based on alignment - int startX = 0; - switch (alignment) { - case TextAlignment::Left: - startX = 0; - break; - case TextAlignment::Center: - startX = (scaled.width - lineWidth) / 2; - break; - case TextAlignment::Right: - startX = scaled.width - lineWidth; - break; - } - - // Render the line - int x = startX; - int startY = verticalOffset + currentLineStartY + baseline + (lineIndex * lineHeight); - - for (std::size_t i = 0; i < line.size(); ++i) { - int codepoint = line[i]; - - int ax; - int lsb; - stbtt_GetCodepointHMetrics(&font.font, codepoint, &ax, &lsb); - - int c_x1, c_y1, c_x2, c_y2; - stbtt_GetCodepointBitmapBox(&font.font, codepoint, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2); - - int w = c_x2 - c_x1; - int h = c_y2 - c_y1; - - std::vector bitmap(w * h); - stbtt_MakeCodepointBitmap(&font.font, bitmap.data(), w, h, w, scale, scale, codepoint); - - // Only render characters that fit within the scaled bounds - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - int bufferX = x + i + c_x1; - int bufferY = startY + j + c_y1; - - // Only draw pixels that are within our scaled buffer bounds - if (bufferX >= 0 && bufferX < (int)scaled.width && bufferY >= 0 && bufferY < (int)scaled.height) { - buffer[bufferY * scaled.width + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; - } - } - } - - x += (int)(ax * scale); - - if (i + 1 < line.size()) { - x += (int)stbtt_GetCodepointKernAdvance(&font.font, codepoint, line[i+1]); - } - } - - currentLineStartY += lineHeight; - } - } else if (overflowMode == TextOverflowMode::Wrap) { - // Implement word wrapping - std::uint_fast32_t lineHeight = (font.ascent - font.descent) * scale; - std::uint_fast32_t maxLineWidth = scaled.width; - std::uint_fast32_t currentLineStartY = 0; - - // Process text character by character to wrap words - std::string remaining(text); - std::string currentLine; - std::string lastWord; - std::string currentWord; - - while (!remaining.empty()) { - // Find next word boundary - std::size_t nextSpace = remaining.find(' '); - std::size_t nextNewline = remaining.find('\n'); - - // Find the earliest delimiter - std::size_t delimiterPos = std::string_view::npos; - if (nextSpace != std::string_view::npos) { - delimiterPos = nextSpace; - } - if (nextNewline != std::string_view::npos && (delimiterPos == std::string_view::npos || nextNewline < delimiterPos)) { - delimiterPos = nextNewline; - } - - // Extract word - if (delimiterPos != std::string_view::npos) { - currentWord = remaining.substr(0, delimiterPos); - remaining = remaining.substr(delimiterPos + 1); - } else { - currentWord = remaining; - remaining = {}; - } - - // Check if adding this word would exceed the line width - std::uint_fast32_t wordWidth = 0; - for (const char c : currentWord) { - int advance, lsb; - stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb); - wordWidth += (int)(advance * scale); - } - - // Add kerning for spaces between words - if (!currentLine.empty() && !currentWord.empty()) { - int lastChar = currentLine.back(); - int firstChar = currentWord.front(); - wordWidth += (int)stbtt_GetCodepointKernAdvance(&font.font, lastChar, firstChar); - } - - // If this word alone is wider than the line, force break it - if (wordWidth > maxLineWidth) { - // Force break the word - if (!currentLine.empty()) { - // Render current line - RenderWrappedLine(currentLine, scale, baseline, currentLineStartY, color, font, alignment); - currentLineStartY += lineHeight; - currentLine = {}; - } - - // Break the long word into parts - std::string_view wordPart = currentWord; - while (!wordPart.empty()) { - // Find a good place to break the word - std::size_t breakPos = 0; - std::uint_fast32_t currentWidth = 0; - - for (std::size_t i = 0; i < wordPart.length(); ++i) { - int advance, lsb; - stbtt_GetCodepointHMetrics(&font.font, wordPart[i], &advance, &lsb); - currentWidth += (int)(advance * scale); - - if (currentWidth > maxLineWidth) { - breakPos = i; - break; - } - } - - if (breakPos == 0) { - breakPos = wordPart.length(); - } - - std::string_view part = wordPart.substr(0, breakPos); - // Render the part - RenderWrappedLine(part, scale, baseline, currentLineStartY, color, font, alignment); - currentLineStartY += lineHeight; - wordPart = wordPart.substr(breakPos); - } - } else if (currentLine.empty() || (currentLine.length() + currentWord.length() + 1) * (int)(scale * 1.5f) <= maxLineWidth) { - // Add word to current line - if (!currentLine.empty()) { - currentLine = currentLine + std::string(" ") + currentWord; - } else { - currentLine = currentWord; - } - } else { - // Render current line and start new one - RenderWrappedLine(currentLine, scale, baseline, currentLineStartY, color, font, alignment); - currentLineStartY += lineHeight; - currentLine = currentWord; - } - } - - // Render final line if it exists - if (!currentLine.empty()) { - RenderWrappedLine(currentLine, scale, baseline, currentLineStartY, color, font, alignment); - } - - // Apply vertical alignment to the entire wrapped text - std::uint_fast32_t totalTextHeight = (currentLineStartY + lineHeight); // Total height including last line - - // Calculate vertical offset based on vertical alignment - std::int_fast32_t verticalOffset = 0; - switch (verticalAlignment) { - case VerticalTextAlignment::Top: - verticalOffset = 0; - break; - case VerticalTextAlignment::Middle: - verticalOffset = (scaled.height - totalTextHeight) / 2; - break; - case VerticalTextAlignment::Bottom: - verticalOffset = scaled.height - totalTextHeight; - break; - } - - // Shift all rendered content vertically - if (verticalOffset != 0) { - for (std::uint_fast32_t y = 0; y < scaled.height; ++y) { - for (std::uint_fast32_t x = 0; x < scaled.width; ++x) { - std::uint_fast32_t index = y * scaled.width + x; - if (index < buffer.size()) { - // Move the pixel vertically - std::uint_fast32_t newIndex = (y + verticalOffset) * scaled.width + x; - if (newIndex < buffer.size()) { - buffer[newIndex] = buffer[index]; - } - } - } - } - } - } -} - -// Helper method to render a wrapped line -void TextElement::RenderWrappedLine(const std::string_view line, float scale, int baseline, std::uint_fast32_t startY, Pixel_BU8_GU8_RU8_AU8 color, Font& font, TextAlignment alignment) { - // Calculate line width - std::uint_fast32_t lineWidth = 0; - std::vector lineAdvances; - for (const char c : line) { - int advance, lsb; - stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb); - lineAdvances.push_back(advance); - lineWidth += (int)(advance * scale); - } - - // Determine horizontal position based on alignment - int startX = 0; - switch (alignment) { - case TextAlignment::Left: - startX = 0; - break; - case TextAlignment::Center: - startX = (scaled.width - lineWidth) / 2; - break; - case TextAlignment::Right: - startX = scaled.width - lineWidth; - break; - } - - // Render the line - int x = startX; - int currentY = startY + baseline; - - for (std::size_t i = 0; i < line.size(); ++i) { - int codepoint = line[i]; - - int ax; - int lsb; - stbtt_GetCodepointHMetrics(&font.font, codepoint, &ax, &lsb); - - int c_x1, c_y1, c_x2, c_y2; - stbtt_GetCodepointBitmapBox(&font.font, codepoint, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2); - - int w = c_x2 - c_x1; - int h = c_y2 - c_y1; - - std::vector bitmap(w * h); - stbtt_MakeCodepointBitmap(&font.font, bitmap.data(), w, h, w, scale, scale, codepoint); - - // Only render characters that fit within the scaled bounds - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - int bufferX = x + i + c_x1; - int bufferY = currentY + j + c_y1; - - // Only draw pixels that are within our scaled buffer bounds - if (bufferX >= 0 && bufferX < (int)scaled.width && bufferY >= 0 && bufferY < (int)scaled.height) { - buffer[bufferY * scaled.width + bufferX] = {color.r, color.g, color.b, bitmap[j * w + i]}; - } - } - } - - x += (int)(ax * scale); - - if (i + 1 < line.size()) { - x += (int)stbtt_GetCodepointKernAdvance(&font.font, codepoint, line[i+1]); - } - } -} \ No newline at end of file