AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
ATextBase.h
    1/*
    2 * AUI Framework - Declarative UI toolkit for modern C++20
    3 * Copyright (C) 2020-2025 Alex2772 and Contributors
    4 *
    5 * SPDX-License-Identifier: MPL-2.0
    6 *
    7 * This Source Code Form is subject to the terms of the Mozilla Public
    8 * License, v. 2.0. If a copy of the MPL was not distributed with this
    9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   10 */
   11
   12#pragma once
   13
   14
   15#include <AUI/Util/AWordWrappingEngine.h>
   16#include "AViewContainer.h"
   17#include "AUI/Font/IFontView.h"
   18#include <initializer_list>
   19#include <variant>
   20#include <AUI/Enum/WordBreak.h>
   21
   22namespace aui::detail {
   23    class TextBaseEntry: public AWordWrappingEngineBase::Entry {
   24    public:
   25        virtual size_t getCharacterCount() = 0;
   26        virtual glm::ivec2 getPosByIndex(size_t characterIndex) = 0;
   27        virtual void appendTo(AString& dst) = 0;
   28        virtual void erase(size_t begin, AOptional<size_t> end) {}
   29
   30        struct StopLineScanningHint{};
   31        using HitTestResult = std::variant<std::nullopt_t, size_t, StopLineScanningHint>;
   32        virtual HitTestResult hitTest(glm::ivec2 position) {
   33            return std::nullopt;
   34        }
   35    };
   36
   37    class CharEntry: public TextBaseEntry {
   38    private:
   39        IFontView* mText;
   40        char32_t mChar;
   41        glm::ivec2 mPosition;
   42
   43    public:
   44        CharEntry(IFontView* text, char32_t ch)
   45                : mText(text), mChar(ch) {}
   46
   47        glm::ivec2 getSize() override {
   48            return { mText->getFontStyle().getCharacter(mChar).advanceX, mText->getFontStyle().size };
   49        }
   50
   51        void setPosition(glm::ivec2 position) override {
   52            mPosition = position;
   53        }
   54
   55        const glm::ivec2& getPosition() const {
   56            return mPosition;
   57        }
   58
   59        char32_t getChar() const {
   60            return mChar;
   61        }
   62
   63        size_t getCharacterCount() override {
   64            return 1;
   65        }
   66
   67        glm::ivec2 getPosByIndex(size_t characterIndex) override {
   68            return mPosition + glm::ivec2{characterIndex * mText->getFontStyle().getCharacter(mChar).advanceX, 0};
   69        }
   70
   71        void appendTo(AString& dst) override {
   72            dst += mChar;
   73        }
   74    };
   75    class WordEntry: public TextBaseEntry {
   76    protected:
   77        IFontView* mText;
   78        AString mWord;
   79        glm::ivec2 mPosition{};
   80
   81    public:
   82        WordEntry(IFontView* text, AString word)
   83                : mText(text), mWord(std::move(word)){}
   84
   85        glm::ivec2 getSize() override {
   86            return { mText->getFontStyle().getWidth(mWord), mText->getFontStyle().size };
   87        }
   88
   89        const AString& getWord() const {
   90            return mWord;
   91        }
   92
   93        AString& getWord() {
   94            return mWord;
   95        }
   96
   97        [[nodiscard]]
   98        glm::ivec2 getPosition() const {
   99            return mPosition;
  100        }
  101
  102        void setPosition(glm::ivec2 position) override {
  103            TextBaseEntry::setPosition(position);
  104            mPosition = position;
  105        }
  106
  107        size_t getCharacterCount() override {
  108            return mWord.size();
  109        }
  110
  111        glm::ivec2 getPosByIndex(size_t characterIndex) override {
  112            return mPosition + glm::ivec2{mText->getFontStyle().getWidth(mWord.begin(), mWord.begin() + long(characterIndex)), 0};
  113        }
  114
  115        void appendTo(AString& dst) override {
  116            dst += mWord;
  117        }
  118
  119        void erase(size_t begin, AOptional<size_t> end) override {
  120            mWord.erase(mWord.begin() + long(begin), mWord.begin() + long(end.valueOr(mWord.length())));
  121        }
  122    };
  123
  124    class WhitespaceEntry: public TextBaseEntry {
  125    private:
  126        IFontView* mText;
  127
  128    public:
  129        WhitespaceEntry(IFontView* text) : mText(text) {}
  130
  131        glm::ivec2 getSize() override {
  132            return { mText->getFontStyle().getSpaceWidth(), mText->getFontStyle().size };
  133        }
  134
  135        bool escapesEdges() override {
  136            return true;
  137        }
  138
  139        ~WhitespaceEntry() override = default;
  140
  141        size_t getCharacterCount() override {
  142            return 1;
  143        }
  144
  145        glm::ivec2 getPosByIndex(size_t characterIndex) override {
  146            throw AException("unimplemented");
  147        }
  148
  149        void appendTo(AString& dst) override {
  150            dst += ' ';
  151        }
  152    };
  153
  154    class NextLineEntry: public TextBaseEntry {
  155    private:
  156        IFontView* mText;
  157
  158    public:
  159        NextLineEntry(IFontView* text) : mText(text) {}
  160
  161        bool forcesNextLine() const override {
  162            return true;
  163        }
  164
  165        glm::ivec2 getSize() override {
  166            return {0, mText->getFontStyle().size};
  167        }
  168
  169        ~NextLineEntry() override = default;
  170
  171        size_t getCharacterCount() override {
  172            return 1;
  173        }
  174
  175        glm::ivec2 getPosByIndex(size_t characterIndex) override {
  176            throw AException("unimplemented");
  177        }
  178
  179        void appendTo(AString& dst) override {
  180            dst += '\n';
  181        }
  182    };
  183}
  184
  188template<typename WordWrappingEngine = AWordWrappingEngine<>>
  189class API_AUI_VIEWS ATextBase: public AViewContainerBase, public IFontView {
  190public:
  191    using Entries = typename WordWrappingEngine::Entries;
  192
  193    ATextBase() = default;
  194    ~ATextBase() override = default;
  195    void render(ARenderContext context) override {
  196        AViewContainerBase::render(context);
  197
  198        doDrawString(context);
  199    }
  200
  201    void doDrawString(ARenderContext& context) {
  202        if (!mPrerenderedString) {
  203            prerenderString(context);
  204        }
  205        if (mPrerenderedString) {
  206            RenderHints::PushColor c(context.render);
  207            context.render.setColor(getTextColor());
  208            mPrerenderedString->draw();
  209        }
  210    }
  211
  212    void setSize(glm::ivec2 size) override {
  213        bool widthDiffers = size.x != getWidth();
  214        AViewContainerBase::setSize(size);
  215        if (widthDiffers) {
  216            mPrerenderedString = nullptr;
  217            markMinContentSizeInvalid();
  218        }
  219    }
  220
  221    int getContentMinimumWidth() override {
  222        if (expanding()->x != 0 || mFixedSize.x != 0) {
  223            // there's no need to calculate min size because width is defined.
  224            return 0;
  225        }
  226
  227        // if width is not defined, when we try to occupy as much space as possible, only restricted by max size.
  228        // basically, it is drastically simplified version of perform layout.
  229        int max = 0;
  230        int accumulator = 0;
  231        const auto paddedMaxSize = mMaxSize.x - mPadding.horizontal();
  232        for (const auto& e : mEngine.entries()) {
  233            if (e->forcesNextLine()) {
  234                max = glm::max(max, accumulator);
  235                accumulator = 0;
  236                continue;
  237            }
  238            if (accumulator + e->getSize().x > paddedMaxSize) {
  239                if (accumulator == 0) {
  240                    return mMaxSize.x;
  241                }
  242                // there's no need to calculate min size further.
  243                goto ret;
  244            }
  245            accumulator += e->getSize().x;
  246        }
  247        ret:
  248        return glm::max(max, accumulator);
  249    }
  250    int getContentMinimumHeight() override {
  251        if (!mPrerenderedString) {
  252            performLayout();
  253        }
  254
  255        if (auto engineHeight = mEngine.height()) {
  256            return *engineHeight;
  257        }
  258
  259        return 0;
  260    }
  261
  262    void invalidateFont() override {
  263        mPrerenderedString.reset();
  264        markMinContentSizeInvalid();
  265    }
  266
  267protected:
  268    void commitStyle() override {
  269        AView::commitStyle();
  270        commitStyleFont();
  271    }
  272
  273    void invalidateAllStyles() override {
  274        invalidateAllStylesFont();
  275        AViewContainerBase::invalidateAllStyles();
  276    }
  277
  278    virtual void fillStringCanvas(const _<IRenderer::IMultiStringCanvas>& canvas) = 0;
  279
  280    void prerenderString(ARenderContext ctx) {
  281        performLayout();
  282        {
  283            auto multiStringCanvas = ctx.render.newMultiStringCanvas(getFontStyle());
  284            fillStringCanvas(multiStringCanvas);
  285            /*
  286            */
  287            mPrerenderedString = multiStringCanvas->finalize();
  288        }
  289    }
  290
  291    virtual void clearContent() {
  292        removeAllViews();
  293        mPrerenderedString = nullptr;
  294    }
  295
  296
  297protected:
  298    WordWrappingEngine mEngine;
  299
  300    _<IRenderer::IPrerenderedString> mPrerenderedString;
  301
  302
  303    void performLayout() {
  304        APerformanceSection s("ATextBase::performLayout");
  305        mEngine.setTextAlign(getFontStyle().align);
  306        mEngine.setLineHeight(getFontStyle().lineSpacing);
  307        mEngine.performLayout({mPadding.left, mPadding.top }, getSize() - glm::ivec2{mPadding.horizontal(), mPadding.vertical()});
  308    }
  309};
T valueOr(F &&alternative) const
value or alternative (either value or callback)
Definition AOptional.h:234
void render(ARenderContext context) override
Draws this AView. Noone should call this function except rendering routine.
Definition ATextBase.h:195
int getContentMinimumWidth() override
Definition ATextBase.h:221
void invalidateAllStyles() override
Invalidates all styles, causing to iterate over all rules in global and parent stylesheets.
Definition ATextBase.h:273
int getContentMinimumHeight() override
Definition ATextBase.h:250
void removeAllViews()
Remove all views from container.
void invalidateAllStyles() override
Invalidates all styles, causing to iterate over all rules in global and parent stylesheets.
void render(ARenderContext context) override
Draws this AView. Noone should call this function except rendering routine.
auto expanding() const
Expansion coefficient. Hints layout manager how much this AView should be extended relative to other ...
Definition AView.h:134
glm::ivec2 mFixedSize
Fixed size.
Definition AView.h:1045
glm::ivec2 getSize() const noexcept
Size, including content area, border and padding.
Definition AView.h:217
glm::ivec2 mMaxSize
Maximal size.
Definition AView.h:1040
ABoxFields mPadding
Padding, which defines the spacing around content area inside the view. Processed by AView implementa...
Definition AView.h:1059
Interface of a AView that works with fonts (i.e., ALabel, ATextField, AText, etc.....
Definition IFontView.h:19
virtual _< IMultiStringCanvas > newMultiStringCanvas(const AFontStyle &style)=0
Creates new canvas for batching multiple prerender string calls.
void setColor(const AColor &color)
Sets the color which is multiplied with any brush. Unlike setColorForced, the new color is multiplied...
Definition IRenderer.h:432
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:179
class_of c
Selects views that are of the specified classes.
Definition class_of.h:84
Render context passed to AView::render.
Definition ARenderContext.h:43