aboutsummaryrefslogtreecommitdiff
path: root/include/Text.hpp
blob: 6a4ed9c612da5fc06cd72c8c6d3ba0baaee4f32e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#pragma once

#include <SFML/Graphics/VertexArray.hpp>
#include <SFML/System/String.hpp>
#include <SFML/System/Clock.hpp>
#include <vector>
#include "types.hpp"
#include <assert.h>

namespace sf {
    class Font;
    class Event;
    class RenderTarget;
}

namespace QuickMedia
{
    struct StringViewUtf32 {
        const u32 *data;
        size_t size;

        StringViewUtf32() : data(nullptr), size(0) {}
        StringViewUtf32(const u32 *data, usize size) : data(data), size(size) {}

        size_t find(const StringViewUtf32 &other, size_t offset = 0) const;

        u32 operator [] (usize index) const {
            assert(index < size);
            return data[index];
        }
    };

    struct TextElement
    {
        enum class Type
        {
            TEXT
        };
        
        TextElement() {}
        TextElement(const StringViewUtf32 &_text, Type _type) : text(_text), type(_type), is_japanese(false) {}
        
        StringViewUtf32 text;
        sf::Vector2f position;
        Type type;
        //bool ownLine; // Currently only used for emoji, to make emoji bigger when it's the only thing on a line
        bool is_japanese;
    };

    struct VertexRef {
        int vertices_index;     // index to |vertices| VertexArray
        int index;              // index within vertices[vertices_index]
        int line;
        sf::Uint32 codepoint;
    };
    
    class Text
    {
    public:
        Text(const sf::Font *font, const sf::Font *cjk_font);
        Text(sf::String str, const sf::Font *font, const sf::Font *cjk_font, unsigned int characterSize, float maxWidth);
        
        void setString(sf::String str);
        const sf::String& getString() const;
        void appendText(sf::String str);
        
        void setPosition(float x, float y);
        void setPosition(const sf::Vector2f &position);
        sf::Vector2f getPosition() const;
        
        void setMaxWidth(float maxWidth);
        float getMaxWidth() const;
        
        void setCharacterSize(unsigned int characterSize);
        unsigned int getCharacterSize() const;
        
        const sf::Font* getFont() const;
        
        void setFillColor(sf::Color color);
        void setLineSpacing(float lineSpacing);
        void setCharacterSpacing(float characterSpacing);
        void setEditable(bool editable);
        bool isEditable() const;
        // Note: only call after initial updateGeometry or draw. TODO: Automatically do this internally
        void moveCaretToEnd();

        // Note: won't update until @draw is called
        float getWidth() const;
        
        // Note: won't update until @draw is called
        float getHeight() const;
        
        void processEvent(const sf::Event &event);
        
        // Performs culling. @updateGeometry is called even if text is not visible if text is dirty, because updateGeometry might change the dimension of the text and make is visible.
        // Returns true if text was drawn on screen (if text is within window borders)
        bool draw(sf::RenderTarget &target);

        void updateGeometry(bool update_even_if_not_dirty = false);
    private:
        enum class CaretMoveDirection : u8
        {
            NONE,
            UP,
            DOWN,
            HOME,
            END,
            LEFT,
            RIGHT
        };

        void updateCaret();
        int getStartOfLine(int startIndex) const;
        int getEndOfLine(int startIndex) const;
        
        int getPreviousLineClosestPosition(int startIndex) const;
        int getNextLineClosestPosition(int startIndex) const;

        void splitTextByFont();

        float get_text_quad_left_side(const VertexRef &vertex_ref) const;
        float get_text_quad_right_side(const VertexRef &vertex_ref) const;
        // If the index is past the end, then the caret offset is the right side of the last character, rather than the left side
        float get_caret_offset_by_caret_index(int index) const;
        VertexRef& get_vertex_ref_clamp(int index);
        // Takes into consideration if index is the last vertex and the last vertex is a newline, then it should be on its own line
        int get_vertex_line(int index) const;
    private:
        sf::String str; // TODO: Remove this for non-editable text???
        const sf::Font *font;
        const sf::Font *cjk_font;
        unsigned int characterSize;
        sf::VertexArray vertices[2];
        float maxWidth;
        sf::Vector2f position;
        sf::Color color;
        sf::Color urlColor;
        bool dirty;
        bool dirtyText;
        bool dirtyCaret;
        bool editable;
        bool visible;
        CaretMoveDirection caretMoveDirection;
        sf::FloatRect boundingBox;
        float lineSpacing;
        float characterSpacing;
        std::vector<TextElement> textElements;
        
        int caretIndex;
        float caret_offset_x;
        sf::Vector2f caretPosition;
        sf::Clock lastSeenTimer;
        sf::Vector2u renderTargetSize;

        std::vector<VertexRef> vertices_linear; // TODO: Use textElements instead
    };
}